home *** CD-ROM | disk | FTP | other *** search
/ SGI Freeware 1999 August / SGI Freeware 1999 August.iso / dist / fw_xemacs.idb / usr / freeware / lib / xemacs-20.4 / lisp / modes / strokes.el.z / strokes.el
Encoding:
Text File  |  1998-05-21  |  81.3 KB  |  2,090 lines

  1. ;;; strokes.el    -- Control XEmacs through mouse strokes --
  2. ;;  Thursday September 4 12:40:41 EDT 1997
  3.  
  4. ;; Copyright (C) 1997 Free Software Foundation, Inc.
  5.  
  6. ;; Author: David Bakhash <cadet@mit.edu>
  7. ;; Maintainer: David Bakhash <cadet@mit.edu>
  8. ;; Version: 2.4-beta
  9. ;; Created: 12 April 1997
  10. ;; Keywords: lisp, mouse, extensions
  11.  
  12. ;; This file is part of XEmacs.
  13.  
  14. ;; XEmacs is free software; you can redistribute it and/or modify it
  15. ;; under the terms of the GNU General Public License as published by
  16. ;; the Free Software Foundation; either version 2 of the License, or
  17. ;; (at your option) any later version.
  18.  
  19. ;; XEmacs program is distributed in the hope that it will be useful,
  20. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  22. ;; General Public License for more details.
  23.  
  24. ;; You should have received a copy of the GNU General Public License
  25. ;; along with XEmacs; see the file COPYING.  If not, write to the Free
  26. ;; Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
  27. ;; 02111-1307, USA.
  28.  
  29. ;;; Synched up with: Not in FSF.
  30.  
  31. ;;; Commentary:
  32.  
  33. ;; This package is written for for XEmacs v20.*.  This is the strokes
  34. ;; package.  It is intended to allow the user to control XEmacs by
  35. ;; means of mouse strokes.  Once strokes is loaded, you can always get
  36. ;; help be invoking `strokes-help':
  37.  
  38. ;; > M-x strokes-help
  39.  
  40. ;; and you can learn how to use the package.  A mouse stroke, for now,
  41. ;; can be defined as holding the middle button, for instance, and then
  42. ;; moving the mouse in whatever pattern you wish, which you have set
  43. ;; XEmacs to understand as mapping to a given command.  For example,
  44. ;; you may wish the have a mouse stroke that looks like a capital `C'
  45. ;; which means `copy-region-as-kill'.  Treat strokes just like you do
  46. ;; key bindings.  For example, XEmacs sets key bindings globally with
  47. ;; the `global-set-key' command.  Likewise, you can do
  48.  
  49. ;; > M-x global-set-stroke
  50.  
  51. ;; to interactively program in a stroke.  It would be wise to set the
  52. ;; first one to this very command, so that from then on, you invoke
  53. ;; `global-set-stroke' with a stroke.  likewise, there may eventually
  54. ;; be a `local-set-stroke' command, also analogous to `local-set-key'.
  55.  
  56. ;; You can always unset the last stroke definition with the command
  57.  
  58. ;; > M-x strokes-unset-last-stroke
  59.  
  60. ;; and the last stroke that was added to `strokes-global-map' will be
  61. ;; removed.
  62.  
  63. ;; Other analogies between strokes and key bindings are as follows:
  64.  
  65. ;;    1) To describe a stroke binding, you can type
  66.  
  67. ;;       > M-x describe-stroke
  68.  
  69. ;;       analogous to `describe-key'.  It's also wise to have a
  70. ;;       stroke, like an `h', for help, or a `?', mapped to
  71. ;;       `describe-stroke'.
  72.  
  73. ;;    2) stroke bindings are set internally through the Lisp function
  74. ;;       `define-stroke', similar to the `define-key' function.  some
  75. ;;       examples for a 3x3 stroke grid would be
  76.  
  77. ;;       (define-stroke c-mode-stroke-map
  78. ;;                      '((0 . 0) (1 . 1) (2 . 2))
  79. ;;                      'kill-region)
  80. ;;       (define-stroke strokes-global-map
  81. ;;                      '((0 . 0) (0 . 1) (0 . 2) (1 . 2) (2 . 2))
  82. ;;                      'list-buffers)
  83.  
  84. ;;       however, if you would probably just have the user enter in
  85. ;;       the stroke interactively and then set the stroke to whatever
  86. ;;       he/she entered. The Lisp function to interactively read a
  87. ;;       stroke is `strokes-read-stroke'.  This is especially helpful
  88. ;;       when you're on a fast computer that can handle a 9x9 stroke
  89. ;;       grid.
  90.  
  91. ;;       NOTE: only global stroke bindings are currently implemented,
  92. ;;       however mode- and buffer-local stroke bindings may eventually
  93. ;;       be implemented in a future version.
  94.  
  95. ;; The important variables to be aware of for this package are listed
  96. ;; below.  They can all be altered through the customizing package via
  97.  
  98. ;; > M-x customize
  99.  
  100. ;; and customizing the group named `strokes'.  You can also read
  101. ;; documentation on the variables there.
  102.  
  103. ;; `strokes-minimum-match-score' (determines the threshold of error
  104. ;; that makes a stroke acceptable or unacceptable.  If your strokes
  105. ;; aren't matching, then you should raise this variable.
  106.  
  107. ;; `strokes-grid-resolution' (determines the grid dimensions that you
  108. ;; use when defining/reading strokes.  The finer the grid your
  109. ;; computer can handle, the more you can do, but even a 3x3 grid is
  110. ;; pretty cool.)  The default value (7) should be fine for most decent
  111. ;; computers.  NOTE: This variable should not be set to a number less
  112. ;; than 3.
  113.  
  114. ;; `strokes-display-strokes-buffer' will allow you to hide the strokes
  115. ;; buffer when doing simple strokes.  This is a speedup for slow
  116. ;; computers as well as people who don't want to see their strokes.
  117.  
  118. ;; If you find that your mouse is accelerating too fast, you can
  119. ;; execute the UNIX X command to slow it down.  A good possibility is
  120.  
  121. ;; % xset m 5/4 8
  122.  
  123. ;; which seems, heuristically, to work okay, without much disruption.
  124.  
  125. ;; Whenever you load in the strokes package, you will be able to save
  126. ;; what you've done upon exiting XEmacs.  You can also do
  127.  
  128. ;; > M-x save-strokes
  129.  
  130. ;; and it will save your strokes in ~/.strokes, or you may wish to
  131. ;; change this by setting the variable `strokes-file'.
  132.  
  133. ;; Note that internally, all of the routines that are part of this
  134. ;; package are able to deal with complex strokes, as they are a
  135. ;; superset of simple strokes.  However, the default of this package
  136. ;; will map mouse button2 to the command `strokes-do-stroke', and NOT
  137. ;; `strokes-do-complex-stroke'.  If you wish to use complex strokes,
  138. ;; you will have to override this key mapping.  Complex strokes are
  139. ;; terminated with mouse button3.  The strokes package will not
  140. ;; interfere with `mouse-yank', but you may want to examine how this
  141. ;; is done (see the variable `strokes-click-command')
  142.  
  143. ;; To get strokes to work as part of your your setup, then you'll have
  144. ;; put the strokes package in your load-path (preferably
  145. ;; byte-compiled) and then add the following to your .emacs file (or
  146. ;; wherever you put XEmacs-specific startup preferences):
  147.  
  148. ;; (and (console-on-window-system-p)
  149. ;;      (require 'strokes))
  150.  
  151. ;; Once loaded, you can start stroking.  You can also toggle between
  152. ;; strokes mode by simple typing
  153.  
  154. ;; > M-x strokes-mode
  155.  
  156. ;; I am now in the process of porting this package to Emacs.  I also
  157. ;; hope that, with the help of others, this package will be useful in
  158. ;; entering in pictographic-like language text using the mouse
  159. ;; (i.e. Korean).  Japanese and Chinese are a bit trickier, but I'm
  160. ;; sure that with help it can be done.  The next version will allow
  161. ;; the user to enter strokes which "remove the pencil from the paper"
  162. ;; so to speak, so one character can have multiple strokes.
  163.  
  164. ;; You can read more about strokes at:
  165.  
  166. ;; http://www.mit.edu/people/cadet/strokes-help.html
  167.  
  168. ;; If you're interested in using strokes for writing English into
  169. ;; XEmacs using strokes, then you'll want to read about it on the web
  170. ;; page above or just download from
  171. ;; http://www.mit.edu/people/cadet/strokes-abc.el, which is nothing
  172. ;; but a file with some helper commands for inserting alphanumerics
  173. ;; and punctuation.
  174.  
  175. ;; Great thanks to Rob Ristroph for his generosity in letting me use
  176. ;; his PC to develop this, Jason Johnson for his help in algorithms,
  177. ;; Euna Kim for her help in Korean, and massive thanks to the helpful
  178. ;; guys on the help instance on athena (zeno, jered, amu, gsstark,
  179. ;; ghudson, etc) Special thanks to Steve Baur, Kyle Jones, and Hrvoje
  180. ;; Niksic for all their help.  And special thanks to Dave Gillespie
  181. ;; for all the elisp help--he is responsible for helping me use the cl
  182. ;; macros at (near) max speed.
  183.  
  184. ;; Tasks: (what I'm getting ready for future version)...
  185. ;; 2) use 'strokes-read-complex-stroke for korean, etc.
  186. ;; 4) buffer-local 'strokes-local-map, and mode-stroke-maps would be nice
  187. ;; 6) add some hooks, like `strokes-read-stroke-hook'
  188. ;; 7) See what people think of the factory settings.  Should I change
  189. ;;    them?  They're all pretty arbitrary in a way.  I guess they
  190. ;;    should be minimal, but computers are getting lots faster, and
  191. ;;    if I choose the defaults too conservatively, then strokes will
  192. ;;    surely disappoint some people on decent machines (until they
  193. ;;    figure out M-x customize).  I need feedback.
  194. ;; Other: I always have the most beta version of strokes, so if you
  195. ;;        want it just let me know.
  196.  
  197. ;;; Change Log:
  198.  
  199. ;; 1.3: provided user variable `strokes-use-strokes-buffer' to let
  200. ;;      users hide the strokes and strokes buffer when entering simple
  201. ;;      strokes.
  202. ;; 1.3: cleaned up most leaks.
  203. ;; 1.3: with Jari Aalto's help, cleaned up overall program.
  204. ;; 1.3: added `strokes-help' for help on strokes
  205. ;; 1.3: fixed 'strokes-load-hook bug
  206. ;; 1.3: email address change: now <cadet@mit.edu>
  207. ;; 1.3: added `strokes-report-bug' based on efs/dired's
  208. ;;      `dired-report-bug'
  209. ;; 1.3: added more dialog-box queries for mouse-event stuff.
  210. ;; 1.4: allowed strokes to invoke kbd macros as well (thanks gsstark!)
  211. ;; 2.0: fixed up ordering of certain functions.
  212. ;; 2.0: fixed bug applying to strokes in dedicated and minibuffer
  213. ;;      windows.
  214. ;; 2.0: punted the C-h way of invoking strokes help routines.
  215. ;; 2.0: fixed `strokes-define-stroke' so it would error check against
  216. ;;      defining strokes that were too short (really clicks) 2.0:
  217. ;;      added `strokes-toggle-strokes-buffer' interactive function
  218. ;; 2.0: added `customize' support, thanks to patch from Hrvoje
  219. ;;      (thanks)
  220. ;; 2.1: strokes no longer forces `mouse-yank-at-point' to t on
  221. ;;      mouse-yank (i.e. `mouse-yank-at-point' is up to you again)
  222. ;; 2.1: toggling strokes-mode off and then back on no longer deletes
  223. ;;      the strokes that you programmed in but didn't save before
  224. ;;      toggling off strokes-mode.
  225. ;; 2.1: advised may functions for modes like VM and w3 so that they
  226. ;;      too can use strokes, while still maintaining old button2
  227. ;;      functionality.
  228. ;; 2.1: with Steve's help, got the autoload for `strokes-mode' and
  229. ;;      fixed up the package so loading it does not enable strokes
  230. ;;      until user calls `strokes-mode'.
  231. ;; 2.2: made sure that abbrev-mode was off in the ` *strokes*' buffer
  232. ;; 2.2: added more dired advice for mouse permissions commands
  233. ;; 2.2: added some checks to see if saving strokes is really necessary
  234. ;;      so the user doesn't get prompted aimlessly.
  235. ;; 2.2: change the `strokes-lift' symbol to a keyword of value
  236. ;;      `:strokes-lift' for legibility.  IF YOUR OLD STROKES DON'T
  237. ;;      WORK, THIS IS PROBABLY WHY.
  238. ;; 2.2: I might have to change this back to `'strokes-lift' because
  239. ;;      the keyword fails in emacs, though I don't know why.
  240. ;; 2.2: `strokes-describe-stroke' is nicer during recursive edits
  241. ;; 2.2: provided `strokes-fill-stroke' to fill in empty spaces of strokes
  242. ;;      as an important step towards platform (speed) independence.
  243. ;;      Because of this, I moved the global setting of
  244. ;;      `strokes-last-stroke' from
  245. ;;      `strokes-eliminate-consecutive-redundancies' to
  246. ;;      `strokes-fill-stroke' since the latter comes later in
  247. ;;      processing a user stroke.
  248. ;; 2.2: Finally changed the defaults, so now `strokes-grid-resolution' is 9
  249. ;;      and `strokes-minimum-match-score' is 1000 by default.  This
  250. ;;      will surely mess some people up, but if so, just set it back
  251. ;;      w/ M-x customize.
  252. ;; 2.2: Fixed up the mechanism for updating the
  253. ;;      `strokes-window-configuration'.  Now it only uses one function
  254. ;;      (`strokes-update-window-configuration') which does it all, and
  255. ;;      much more efficiently (thanks RMS!).
  256. ;; 2.2  Fixed up the appearance of the *strokes* buffer so that there
  257. ;;      are no ugly line truncations, and I got rid of the bug which
  258. ;;      would draw the stroke on the wrong line.  I still wish that
  259. ;;      `event-closest-point' was smarter.  In fact,
  260. ;;      `event-closest-point' does *not* do what its name suggests.
  261. ;; 2.3  Added more to `strokes-update-window-configuration' so it goes
  262. ;;      to hell less often
  263. ;; 2.3 `strokes-mode' no longer will undefined keys unless it's sure
  264. ;;      that the user had had them mapped to a strokes command.
  265. ;; 2.3  Added more magic autoload statements so strokes work more
  266. ;;      smoothly.  similarly, I made strokes-mode turn itself on when
  267. ;;      the user defines a stroke (thanks Hrvoje).
  268. ;; 2.3  Added "Strokes" to the modeline when strokes is on, and allow
  269. ;;      toggling strokes with mouse button2.
  270. ;; 2.3  Added `list-strokes', which is a really nice function which
  271. ;;      graphically lists all the strokes that the user has defined
  272. ;;      and their corresponding commands.  `list-strokes' will
  273. ;;      appropriately colorize the pixmaps to display some time info.
  274. ;; 2.4  Added all new functionality to strokes by allowing the user to
  275. ;;      enter strokes in graphically into XEmacs, allowing true graphic
  276. ;;      editing, Chinese/Japanese, etc.  User simply uses C-button2 to
  277. ;;      draw strokes (function: `strokes-compose-complex-stroke').  Then
  278. ;;      after the glyph gets inserted into the current buffer at (point),
  279. ;;      the use can treat that glyph as any other character, and
  280. ;;      copy/paste/delete/undo, etc.  Also, when the user would like to
  281. ;;      save/send the glyphs (to other XEmacs users, of course), he/she
  282. ;;      can use the helper functions:
  283. ;;
  284. ;;      i.  M-x strokes-encode-buffer -- Ascii-encodes and compresses
  285. ;;                                       strokes to base-64.
  286. ;;      ii. M-x strokes-decode-buffer -- Decodes ascii-encoded strokes
  287. ;;                                       back into glyphs.
  288. ;; 2.4  With help from Kyle fixed the itimer (timeout event) bug, where I
  289. ;;      forgot to check for timeouts.
  290. ;; 2.4  Around this time, made a successful port of strokes.el for emacs.
  291. ;; 2.4  Made added `strokes-xpm-header' as a variable.
  292. ;; 2.4  Changed the default value of `strokes-character' from `o' to
  293. ;;      `@' since it looks nicer when drawn.
  294. ;; 2.4  Changed `strokes-click-p' so that it considers only a stroke
  295. ;;      of length <= 1 a click, as opposed to a length 2 being a
  296. ;;      click.
  297. ;; 2.4  Totally made the the function `strokes-read-stroke' (and a bit
  298. ;;      on `strokes-read-complex-stroke') more efficient and robust,
  299. ;;      making the former use the optional event passed to it, and
  300. ;;      thus not losing the first mouse event position when reading a
  301. ;;      stroke on the fly.
  302. ;; 2.4  Finally fixed the mouse-yank / mouse-yank-at-point bug (after
  303. ;;      months of struggling with it).  I simply inserted a (sit-for 0)
  304. ;;      before the (command-execute strokes-click-command) and that
  305. ;;      patched it up.  I'd thought that it was a kludge, but I later
  306. ;;      found out that it wasn't, as redisplay has several states, and
  307. ;;      command-execute often must decide which of two states must be
  308. ;;      considered when executing a command.  The (sit-for 0) merely
  309. ;;      allowed redisplay to be sure to wait for the ` *strokes*'
  310. ;;      buffer to vanish before executing the command (thanks for the
  311. ;;      explanation of why my frobbing worked Kyle).  Fixing this bug
  312. ;;      also (magically) fixed the bug which prevented strokes from
  313. ;;      executing a stroke in a mode which had it's own binding for
  314. ;;      button-2, such as w3 when the variable
  315. ;;      `strokes-use-strokes-buffer' is non-nil.  It used to be that
  316. ;;      if you chose to view your strokes, then you couldn't use
  317. ;;      strokes properly in modes like VM or w3.  Now you can!
  318. ;; 2.4  Replaced `kill-emacs-hook' with `kill-emacs-query-functions'
  319. ;;      for prompting the user to save his/her strokes, since
  320. ;;      `kill-emacs-hook' was not the right hook to use.
  321. ;; 2.4  Having `strokes-update-window-configuration' bound to
  322. ;;      `select-frame-hook' was a heavy function for such a commonly
  323. ;;      run hook -- especially since event-Xt.c (?) will add the
  324. ;;      eval-event to the event queue.  So the effect was that if XEmacs
  325. ;;      was doing an interpreter-intensive task while the user (re)selected
  326. ;;      the frame n times, then the intensive window config updating
  327. ;;      took place n times.  So to deal, I put in some extra checks to
  328. ;;      see if the frame parameters really changed, making an update
  329. ;;      worthwhile.  See `strokes-update-window-configuration-plist'.
  330. ;; 2.4  For XEmacs 20.*, all hashtables were changed to char-tables,
  331. ;;      since this is more modern, more efficient, and faster.  God only 
  332. ;;      knows how inefficient the hash function was before the advent of 
  333. ;;      char-tables.  I also did this out of necessity since MIT's
  334. ;;      version of XEmacs-20.2 was hashtable-buggy.  
  335.  
  336. ;;; Code:
  337.  
  338. ;;; Requirements and provisions...
  339.  
  340. (autoload 'reporter-submit-bug-report "reporter")
  341. (autoload 'mail-position-on-field "sendmail")
  342. (eval-when-compile
  343.   (mapc 'require '(xpm-mode pp annotations reporter advice view-less)))
  344.  
  345. ;;; Constants...
  346.  
  347. (defconst strokes-version "2.4-beta")
  348.  
  349. (defconst strokes-bug-address "cadet@mit.edu")
  350.  
  351. (defconst strokes-lift :strokes-lift
  352.   "Symbol representing a stroke lift event for complex strokes.
  353. Complex strokes are those which contain two or more simple strokes.
  354. This will be useful for when XEmacs understands Chinese.")
  355.  
  356. (defconst strokes-xpm-header "/* XPM */
  357. static char * stroke_xpm[] = {
  358. /* width height ncolors cpp [x_hot y_hot] */
  359. \"33 33 9 1 26 23\",
  360. /* colors */
  361. \"     c none s none\",
  362. \"*    c #000000 s foreground\",
  363. \"R    c #FFFF00000000\",
  364. \"O    c #FFFF80000000\",
  365. \"Y    c #FFFFFFFF0000\",
  366. \"G    c #0000FFFF0000\",
  367. \"B    c #00000000FFFF\",
  368. \"P    c #FFFF0000FFFF\",
  369. \".    c #45458B8B0000\",
  370. /* pixels */\n"
  371.   "The header to all xpm buffers created by strokes")
  372.  
  373. ;;; user variables...
  374.  
  375. (defgroup strokes nil
  376.   "Control Emacs through mouse strokes."
  377.   :group 'mouse
  378.   :group 'lisp
  379.   :group 'extensions)
  380.  
  381. ;; This is an internal variable, but we defcustom it so Customize can
  382. ;; use it.
  383. ;;;###autoload
  384. (defcustom strokes-mode nil
  385.   "Non-nil when `strokes' is globally enabled."
  386.   :type 'boolean
  387.   :set (lambda (symbol value)
  388.      (strokes-mode (or value 0)))
  389.   :initialize 'custom-initialize-default
  390.   :require 'strokes
  391.   :group 'strokes)
  392.  
  393. (defcustom strokes-modeline-string " Strokes"
  394.   "*Modeline identification when strokes are on \(default is \" Strokes\"\)."
  395.   :type 'string
  396.   :group 'strokes)
  397.  
  398. (defcustom strokes-character ?@
  399.   "*Character used when drawing strokes in the strokes buffer.
  400. \(The default is lower-case `@', which works okay\)."
  401.   :type 'character
  402.   :group 'strokes)
  403.  
  404. (defcustom strokes-minimum-match-score 1000
  405.   "*Minimum score for a stroke to be considered a possible match.
  406. Requiring a perfect match would set this variable to 0.
  407. The default value is 1000, but it's mostly dependent on how precisely
  408. you manage to replicate your user-defined strokes.  It also depends on
  409. the value of `strokes-grid-resolution', since a higher grid resolution
  410. will correspond to more sample points, and thus more distance
  411. measurements.  Usually, this is not a problem since you first set
  412. `strokes-grid-resolution' based on what your computer seems to be able
  413. to handle (though the defaults are usually more than sufficent), and
  414. then you can set `strokes-minimum-match-score' to something that works
  415. for you.  The only purpose of this variable is to insure that if you
  416. do a bogus stroke that really doesn't match any of the predefined
  417. ones, then strokes should NOT pick the one that came closest."
  418.   :type 'integer
  419.   :group 'strokes)
  420.  
  421. (defcustom strokes-grid-resolution 9
  422.   "*Integer defining dimensions of the stroke grid.
  423. The grid is a square grid, where STROKES-GRID-RESOLUTION defaults to
  424. `9', making a 9x9 grid whose coordinates go from (0 . 0) on the top
  425. left to ((STROKES-GRID-RESOLUTION - 1) . (STROKES-GRID-RESOLUTION - 1))
  426. on the bottom right.  The greater the resolution, the more intricate
  427. your strokes can be.
  428. NOTE: This variable should be odd and MUST NOT be less than 3 and need
  429.       not be greater than 33, which is the resolution of the pixmaps.
  430. WARNING: Changing the value of this variable will gravely affect the
  431.          strokes you have already programmed in.  You should try to
  432.          figure out what it should be based on your needs and on how
  433.          quick the particular platform(s) you're operating on, and
  434.          only then start programming in your custom strokes."
  435.   :type 'integer
  436.   :group 'strokes)
  437.  
  438. (defcustom strokes-file "~/.strokes"
  439.   "*File containing saved strokes for stroke-mode (default is ~/.strokes)."
  440.   :type 'file
  441.   :group 'strokes)
  442.  
  443. (defcustom strokes-buffer-name " *strokes*"
  444.   "The buffer that the strokes take place in (default is ` *strokes*')."
  445.   :type 'string
  446.   :group 'strokes)
  447.  
  448. (defcustom strokes-use-strokes-buffer t
  449.   "*If non-nil, the strokes buffer is used and strokes are displayed.
  450. If nil, strokes will be read the same, however the user will not be
  451. able to see the strokes.  This be helpful for people who don't like
  452. the delay in switching to the strokes buffer."
  453.   :type 'boolean
  454.   :group 'strokes)
  455.  
  456. (defcustom strokes-click-command 'mouse-yank
  457.   "*Command to execute when stroke is actually a `click' event.
  458. This is set to `mouse-yank' by default."
  459.   :type 'function
  460.   :group 'strokes)
  461.  
  462. ;;; internal variables...
  463.  
  464. (defvar strokes-window-configuration nil
  465.   "The special window configuration used when entering strokes.
  466. This is set properly in the function `strokes-update-window-configuration'.")
  467.  
  468. (defvar strokes-window-configuration-plist
  469.   (list 'frame nil 'frame-height nil 'frame-width nil)
  470.   "Plist describing the state of the current strokes-window-configuration.
  471. The plist consists of the following keys:
  472.  
  473. 'frame           Frame to draw strokes in.
  474. 'frame-height    Height of the frame.
  475. 'frame-width     Width of the frame.")
  476.  
  477. (defvar strokes-last-stroke nil
  478.   "Last stroke entered by the user.
  479. Its value gets set every time the function
  480. `strokes-fill-stroke' gets called,
  481. since that is the best time to set the variable")
  482.  
  483. (defvar strokes-global-map '()
  484.   "Association list of strokes and their definitions.
  485. Each entry is (STROKE . COMMAND) where STROKE is itself a list of
  486. coordinates (X . Y) where X and Y are lists of positions on the
  487. normalized stroke grid, with the top left at (0 . 0).  COMMAND is the
  488. corresponding interactive function")
  489.  
  490. (defvar strokes-load-hook nil
  491.   "Function or functions to be called when `strokes' is loaded.")
  492.  
  493. ;;; ### NOT IMPLEMENTED YET ###
  494. ;;(defvar edit-strokes-menu
  495. ;;  '("Edit-Strokes"
  496. ;;    ["Add stroke..." strokes-global-set-stroke t]
  497. ;;    ["Delete stroke..." strokes-edit-delete-stroke t]
  498. ;;    ["Change stroke"    strokes-smaller    t]
  499. ;;    ["Change definition"    strokes-larger    t]
  500. ;;    ["[Re]List Strokes chronologically"    strokes-list-strokes    t]
  501. ;;    ["[Re]List Strokes alphabetically"    strokes-list-strokes    t]
  502. ;;    ["Quit"        strokes-edit-quit        t]
  503. ;;    ))
  504.  
  505. ;;; Macros...
  506.  
  507. (defmacro strokes-while-inhibiting-garbage-collector (&rest forms)
  508.   "Execute FORMS without interference from the garbage collector."
  509.   `(let ((gc-cons-threshold 134217727))
  510.      ,@forms))
  511.  
  512. (defsubst strokes-click-p (stroke)
  513.   "Non-nil if STROKE is really click."
  514.   (< (length stroke) 2))
  515.  
  516. ;;; old, but worked pretty good (just in case)...
  517. ;;(defmacro strokes-define-stroke (stroke-map stroke def)
  518. ;;  "Add STROKE to STROKE-MAP alist with given command DEF"
  519. ;;  (list 'if (list '< (list 'length stroke) 3)
  520. ;;    (list 'error
  521. ;;          "That's a click, not a stroke.  See `strokes-click-command'")
  522. ;;    (list 'setq stroke-map (list 'cons (list 'cons stroke def)
  523. ;;                     (list 'remassoc stroke stroke-map)))))
  524.  
  525. (defmacro strokes-define-stroke (stroke-map stroke def)
  526.   "Add STROKE to STROKE-MAP alist with given command DEF."
  527.   `(if (strokes-click-p ,stroke)
  528.        (error "That's a click, not a stroke; see `strokes-click-command'")
  529.      (setq ,stroke-map (cons (cons ,stroke ,def)
  530.                  (remassoc ,stroke ,stroke-map)))))
  531.  
  532. (defalias 'define-stroke 'strokes-define-stroke)
  533.  
  534. (defsubst strokes-square (x)
  535.   "Returns the square of the number X"
  536.   (* x x))
  537.  
  538. (defsubst strokes-distance-squared (p1 p2)
  539.   "Gets the distance (squared) between to points P1 and P2.
  540. P1 and P2 are cons cells in the form (X . Y)."
  541.   (let ((x1 (car p1))
  542.     (y1 (cdr p1))
  543.     (x2 (car p2))
  544.     (y2 (cdr p2)))
  545.     (+ (strokes-square (- x2 x1))
  546.        (strokes-square (- y2 y1)))))
  547.  
  548. ;;; Advice for various functions...
  549.  
  550. ;; I'd originally wanted to write a macro that would just take in the
  551. ;; generic functions which use mouse button2 in various modes.  Most
  552. ;; of them are identical in form: they take an event as the single
  553. ;; argument and then do their thing.  I tried writing a macro that
  554. ;; looked something like this, but failed.  Advice just ain't that
  555. ;; easy.  The one that bugged me the most was `Manual-follow-xref',
  556. ;; because that had &rest arguments, and I didn't know how to work
  557. ;; around it in defadvice.  However, I was able to fix up most of the
  558. ;; important modes (i.e. the ones I use all the time).  One `bug' in
  559. ;; the program that I just can't seem to figure out is why I can only
  560. ;; advise other button2 functions successfully when the variable
  561. ;; `strokes-use-strokes-buffer' is nil.  I did all the
  562. ;; save-excursion/save-window-excursion stuff SPECIFICALLY so that
  563. ;; using the strokes buffer or not would absolutely not affect any
  564. ;; other part of the program.  If someone can figure out how to make
  565. ;; the following advices work w/ regardless of that variable
  566. ;; `strokes-use-strokes-buffer', then that would be a great victory.
  567. ;; If someone out there would be kind enough to make the commented
  568. ;; code below work, I'd be grateful.  By the way, I put the `protect'
  569. ;; keywords there to insure that if a stroke went bad, then
  570. ;; `strokes-click-command' would be set back.  If this isn't
  571. ;; necessary, then feel free to let me know.
  572.  
  573. ;; For what follows, I really wanted something that would work like this:
  574.  
  575. ;;(strokes-fix-button2 'vm-mouse-button-2)
  576.  
  577. ;; Or even better, I could have simply done something like:
  578.  
  579. ;;(mapcar 'strokes-fix-button2
  580. ;;       '(vm-mouse-button-2
  581. ;;          rmail-summary-mouse-goto-msg
  582. ;;        <rest of them>))
  583.  
  584. ;;; With help from Hans (author of advice.el)...
  585. (defmacro strokes-fix-button2-command (command)
  586.   "Fix COMMAND so that it can also work with strokes.
  587. COMMAND must take one event argument.
  588. Example of how one might fix up a command that's bound to button2
  589. and which is an interactive funcion of one event argument:
  590.  
  591. \(strokes-fix-button2-command 'vm-mouse-button-2)"
  592.   (let ((command (eval command)))
  593.     `(progn
  594.        (defadvice ,command (around strokes-fix-button2 compile preactivate)
  595.          ,(format "Fix %s to work with strokes." command)
  596.          (let ((strokes-click-command
  597.                   ',(intern (format "ad-Orig-%s" command))))
  598.              (strokes-do-stroke (ad-get-arg 0)))))))
  599.  
  600. (defvar strokes-insinuated nil)
  601.  
  602. (defun strokes-insinuate ()
  603.   "Insinuate Emacs with strokes advices."
  604.   (unless strokes-insinuated
  605.     (strokes-fix-button2-command 'vm-mouse-button-2)
  606.     (strokes-fix-button2-command 'rmail-summary-mouse-goto-msg)
  607.     (strokes-fix-button2-command 'Buffer-menu-mouse-select)
  608.     (strokes-fix-button2-command 'w3-widget-button-click)
  609.     (strokes-fix-button2-command 'widget-image-button-press)
  610.     (strokes-fix-button2-command 'Info-follow-clicked-node)
  611.     (strokes-fix-button2-command 'compile-mouse-goto-error)
  612.     (strokes-fix-button2-command 'gdbsrc-select-or-yank)
  613.     (strokes-fix-button2-command 'hypropos-mouse-get-doc)
  614.     (strokes-fix-button2-command 'gnus-mouse-pick-group)
  615.     (strokes-fix-button2-command 'gnus-mouse-pick-article)
  616.     (strokes-fix-button2-command 'gnus-article-push-button)
  617.     (strokes-fix-button2-command 'dired-mouse-find-file)
  618.     (strokes-fix-button2-command 'url-dired-find-file-mouse)
  619.     (strokes-fix-button2-command 'dired-u-r-mouse-toggle)
  620.     (strokes-fix-button2-command 'dired-u-w-mouse-toggle)
  621.     (strokes-fix-button2-command 'dired-u-x-mouse-toggle)
  622.     (strokes-fix-button2-command 'dired-g-r-mouse-toggle)
  623.     (strokes-fix-button2-command 'dired-g-w-mouse-toggle)
  624.     (strokes-fix-button2-command 'dired-g-x-mouse-toggle)
  625.     (strokes-fix-button2-command 'dired-o-r-mouse-toggle)
  626.     (strokes-fix-button2-command 'dired-o-w-mouse-toggle)
  627.     (strokes-fix-button2-command 'isearch-yank-x-selection)
  628.     (strokes-fix-button2-command 'occur-mode-mouse-goto)
  629.     (strokes-fix-button2-command 'cvs-mouse-find-file))
  630.   (setq strokes-insinuated t))
  631.  
  632. ;;; I can fix the customize widget button click, but then
  633. ;;; people will get confused when they try to customize
  634. ;;; strokes with the mouse and customize tells them that
  635. ;;; `strokes-click-command' is mapped to `ad-Orig-widget-button-click'
  636. ;;(strokes-fix-button2-command 'widget-button-click)
  637.  
  638. ;;; without the advice, each advised function would look like...
  639. ;;(defadvice vm-mouse-button-2 (around vm-strokes activate protect)
  640. ;;  "Allow strokes to work in VM."
  641. ;;  (if strokes-use-strokes-buffer
  642. ;;      ;; then strokes is no good and we'll have to use the original
  643. ;;      ad-do-it
  644. ;;    ;; otherwise, we can make strokes work too...
  645. ;;    (let ((strokes-click-command 'ad-Orig-vm-mouse-button-2))
  646. ;;      (strokes-do-stroke (ad-get-arg 0)))))
  647.  
  648. ;;; Functions...
  649.  
  650. (defun strokes-lift-p (object)
  651.   "Return non-nil if object is a stroke-lift."
  652.   (eq object strokes-lift))
  653.  
  654. (defun strokes-unset-last-stroke ()
  655.   "Undo the last stroke definition."
  656.   (interactive)
  657.   (let ((command (cdar strokes-global-map)))
  658.     (if (y-or-n-p-maybe-dialog-box
  659.      (format "really delete last stroke definition, defined to `%s'? "
  660.          command))
  661.     (progn
  662.       (setq strokes-global-map (cdr strokes-global-map))
  663.       (message "That stroke has been deleted"))
  664.       (message "Nothing done"))))
  665.  
  666. ;;;###autoload
  667. (defun strokes-global-set-stroke (stroke command)
  668.   "Interactively give STROKE the global binding as COMMAND.
  669. Operated just like `global-set-key', except for strokes.
  670. COMMAND is a symbol naming an interactively-callable function.  STROKE
  671. is a list of sampled positions on the stroke grid as described in the
  672. documentation for the `strokes-define-stroke' function."
  673.   (interactive
  674.    (list
  675.     (and (or strokes-mode (strokes-mode t))
  676.      (strokes-read-complex-stroke
  677.       "Define a new stroke.  Draw with button1 (or 2).  End with button3..."))
  678.     (read-command-or-command-sexp "command to map stroke to: ")))
  679.   (strokes-define-stroke strokes-global-map stroke command))
  680.  
  681. ;;;###autoload
  682. (defalias 'global-set-stroke 'strokes-global-set-stroke)
  683.  
  684. ;;(defun global-unset-stroke (stroke); FINISH THIS DEFUN!
  685. ;;  "delete all strokes matching STROKE from `strokes-global-map',
  686. ;; letting the user input
  687. ;; the stroke with the mouse"
  688. ;;  (interactive
  689. ;;   (list
  690. ;;    (strokes-read-stroke "Enter the stroke you want to delete...")))
  691. ;;  (strokes-define-stroke 'strokes-global-map stroke command))
  692.  
  693. (defun strokes-get-grid-position (stroke-extent position &optional grid-resolution)
  694.   "Map POSITION to a new grid position based on its STROKE-EXTENT and GRID-RESOLUTION.
  695. STROKE-EXTENT as a list \(\(XMIN . YMIN\) \(XMAX . YMAX\)\).
  696. If POSITION is a `strokes-lift', then it is itself returned.
  697. Optional GRID-RESOLUTION may be used in place of STROKES-GRID-RESOLUTION.
  698. The grid is a square whose dimesion is [0,GRID-RESOLUTION)."
  699.   (cond ((consp position)        ; actual pixel location
  700.      (let ((grid-resolution (or grid-resolution strokes-grid-resolution))
  701.            (x (car position))
  702.            (y (cdr position))
  703.            (xmin (caar stroke-extent))
  704.            (ymin (cdar stroke-extent))
  705.            ;; the `1+' is there to insure that the
  706.            ;; formula evaluates correctly at the boundaries
  707.            (xmax (1+ (caadr stroke-extent)))
  708.            (ymax (1+ (cdadr stroke-extent))))
  709.        (cons (floor (* grid-resolution
  710.                (/ (float (- x xmin))
  711.                   (- xmax xmin))))
  712.          (floor (* grid-resolution
  713.                (/ (float (- y ymin))
  714.                   (- ymax ymin)))))))
  715.     ((strokes-lift-p position)    ; stroke lift
  716.      strokes-lift)))
  717.  
  718. (defun strokes-get-stroke-extent (pixel-positions)
  719.   "From a list of absolute PIXEL-POSITIONS, returns absolute spatial extent.
  720. The return value is a list ((XMIN . YMIN) (XMAX . YMAX))."
  721.   (if pixel-positions
  722.       (let ((xmin (caar pixel-positions))
  723.         (xmax (caar pixel-positions))
  724.         (ymin (cdar pixel-positions))
  725.         (ymax (cdar pixel-positions))
  726.         (rest (cdr pixel-positions)))
  727.     (while rest
  728.       (if (consp (car rest))
  729.           (let ((x (caar rest))
  730.             (y (cdar rest)))
  731.         (if (< x xmin)
  732.             (setq xmin x))
  733.         (if (> x xmax)
  734.             (setq xmax x))
  735.         (if (< y ymin)
  736.             (setq ymin y))
  737.         (if (> y ymax)
  738.             (setq ymax y))))
  739.       (setq rest (cdr rest)))
  740.     (let ((delta-x (- xmax xmin))
  741.           (delta-y (- ymax ymin)))
  742.       (if (> delta-x delta-y)
  743.           (setq ymin (- ymin
  744.                 (/ (- delta-x delta-y)
  745.                    2))
  746.             ymax (+ ymax
  747.                 (/ (- delta-x delta-y)
  748.                    2)))
  749.         (setq xmin (- xmin
  750.               (/ (- delta-y delta-x)
  751.                  2))
  752.           xmax (+ xmax
  753.               (/ (- delta-y delta-x)
  754.                  2))))
  755.       (list (cons xmin ymin)
  756.         (cons xmax ymax))))
  757.     nil))
  758.  
  759. (defun strokes-eliminate-consecutive-redundancies (entries)
  760.   "Returns a list with no consecutive redundant entries."
  761.   ;; defun a grande vitesse grace a Dave G.
  762.   (loop for element on entries
  763.         if (not (equal (car element) (cadr element)))
  764.         collect (car element)))
  765. ;;  (loop for element on entries
  766. ;;        nconc (if (not (equal (car el) (cadr el)))
  767. ;;                  (list (car el)))))
  768. ;; yet another (orig) way of doing it...
  769. ;;  (if entries
  770. ;;      (let* ((current (car entries))
  771. ;;         (rest (cdr entries))
  772. ;;         (non-redundant-list (list current))
  773. ;;         (next nil))
  774. ;;    (while rest
  775. ;;      (setq next (car rest))
  776. ;;      (if (equal current next)
  777. ;;          (setq rest (cdr rest))
  778. ;;        (setq non-redundant-list (cons next non-redundant-list)
  779. ;;          current next
  780. ;;          rest (cdr rest))))
  781. ;;    (nreverse non-redundant-list))
  782. ;;    nil))
  783.  
  784. (defun strokes-renormalize-to-grid (positions &optional grid-resolution)
  785.   "Map POSITIONS to a new grid whose dimensions are based on GRID-RESOLUTION.
  786. POSITIONS is a list of positions and stroke-lifts.
  787. Optional GRID-RESOLUTION may be used in place of STROKES-GRID-RESOLUTION.
  788. The grid is a square whose dimesion is [0,GRID-RESOLUTION)."
  789.   (or grid-resolution (setq grid-resolution strokes-grid-resolution))
  790.   (let ((stroke-extent (strokes-get-stroke-extent positions)))
  791.     (mapcar (function
  792.          (lambda (pos)
  793.            (strokes-get-grid-position stroke-extent pos grid-resolution)))
  794.         positions)))
  795.  
  796. (defun strokes-fill-stroke (unfilled-stroke &optional force)
  797.   "Fill in missing grid locations in the list of UNFILLED-STROKE.
  798. If FORCE is non-nil, then fill the stroke even if it's `stroke-click'.
  799. NOTE: This is where the global variable `strokes-last-stroke' is set."
  800.   (setq strokes-last-stroke        ; this is global
  801.     (if (and (strokes-click-p unfilled-stroke)
  802.          (not force))
  803.         unfilled-stroke
  804.       (loop for grid-locs on unfilled-stroke
  805.         nconc (let* ((current (car grid-locs))
  806.                  (current-is-a-point-p (consp current))
  807.                  (next (cadr grid-locs))
  808.                  (next-is-a-point-p (consp next))
  809.                  (both-are-points-p (and current-is-a-point-p
  810.                              next-is-a-point-p))
  811.                  (x1 (and current-is-a-point-p
  812.                       (car current)))
  813.                  (y1 (and current-is-a-point-p
  814.                       (cdr current)))
  815.                  (x2 (and next-is-a-point-p
  816.                       (car next)))
  817.                  (y2 (and next-is-a-point-p
  818.                       (cdr next)))
  819.                  (delta-x (and both-are-points-p
  820.                        (- x2 x1)))
  821.                  (delta-y (and both-are-points-p
  822.                        (- y2 y1)))
  823.                  (slope (and both-are-points-p
  824.                      (if (zerop delta-x)
  825.                          nil ; undefined vertical slope
  826.                        (/ (float delta-y)
  827.                           delta-x)))))
  828.             (cond ((not both-are-points-p)
  829.                    (list current))
  830.                   ((null slope) ; undefinded vertical slope
  831.                    (if (>= delta-y 0)
  832.                    (loop for y from y1 below y2
  833.                      collect (cons x1 y))
  834.                  (loop for y from y1 above y2
  835.                        collect (cons x1 y))))
  836.                   ((zerop slope) ; (= y1 y2)
  837.                    (if (>= delta-x 0)
  838.                    (loop for x from x1 below x2
  839.                      collect (cons x y1))
  840.                  (loop for x from x1 above x2
  841.                        collect (cons x y1))))
  842.                   ((>= (abs delta-x) (abs delta-y))
  843.                    (if (> delta-x 0)
  844.                    (loop for x from x1 below x2
  845.                      collect (cons x
  846.                                (+ y1
  847.                               (round (* slope
  848.                                     (- x x1))))))
  849.                  (loop for x from x1 above x2
  850.                        collect (cons x
  851.                              (+ y1
  852.                             (round (* slope
  853.                                   (- x x1))))))))
  854.                   (t    ; (< (abs delta-x) (abs delta-y))
  855.                    (if (> delta-y 0)
  856.                    (loop for y from y1 below y2
  857.                      collect (cons (+ x1
  858.                               (round (/ (- y y1)
  859.                                     slope)))
  860.                                y))
  861.                  (loop for y from y1 above y2
  862.                        collect (cons (+ x1
  863.                             (round (/ (- y y1)
  864.                                   slope)))
  865.                              y))))))))))
  866.  
  867. (defun strokes-rate-stroke (stroke1 stroke2)
  868.   "Rates STROKE1 with STROKE2 and returns a score based on a distance metric.
  869. Note: the rating is an error rating, and therefore, a return of 0
  870. represents a perfect match.  Also note that the order of stroke
  871. arguments is order-independent for the algorithm used here."
  872.   (if (and stroke1 stroke2)
  873.       (let ((rest1 (cdr stroke1))
  874.         (rest2 (cdr stroke2))
  875.         (err (strokes-distance-squared (car stroke1)
  876.                        (car stroke2))))
  877.     (while (and rest1 rest2)
  878.       (while (and (consp (car rest1))
  879.               (consp (car rest2)))
  880.         (setq err (+ err
  881.              (strokes-distance-squared (car rest1)
  882.                            (car rest2)))
  883.           stroke1 rest1
  884.           stroke2 rest2
  885.           rest1 (cdr stroke1)
  886.           rest2 (cdr stroke2)))
  887.       (cond ((and (strokes-lift-p (car rest1))
  888.               (strokes-lift-p (car rest2)))
  889.          (setq rest1 (cdr rest1)
  890.                rest2 (cdr rest2)))
  891.         ((strokes-lift-p (car rest2))
  892.          (while (consp (car rest1))
  893.            (setq err (+ err
  894.                 (strokes-distance-squared (car rest1)
  895.                               (car stroke2)))
  896.              rest1 (cdr rest1))))
  897.         ((strokes-lift-p (car rest1))
  898.          (while (consp (car rest2))
  899.            (setq err (+ err
  900.                 (strokes-distance-squared (car stroke1)
  901.                               (car rest2)))
  902.              rest2 (cdr rest2))))))
  903.     (if (null rest2)
  904.         (while (consp (car rest1))
  905.           (setq err (+ err
  906.                (strokes-distance-squared (car rest1)
  907.                              (car stroke2)))
  908.             rest1 (cdr rest1))))
  909.     (if (null rest1)
  910.         (while (consp (car rest2))
  911.           (setq err (+ err
  912.                (strokes-distance-squared (car stroke1)
  913.                              (car rest2)))
  914.             rest2 (cdr rest2))))
  915.     (if (or (strokes-lift-p (car rest1))
  916.         (strokes-lift-p (car rest2)))
  917.         (setq err nil)
  918.       err))
  919.     nil))
  920.  
  921. (defun strokes-match-stroke (stroke stroke-map)
  922.   "Finds the best matching command of STROKE in STROKE-MAP.
  923. Returns the corresponding match as (COMMAND . SCORE)."
  924.   (if (and stroke stroke-map)
  925.       (let ((score (strokes-rate-stroke stroke (caar stroke-map)))
  926.         (command (cdar stroke-map))
  927.         (map (cdr stroke-map)))
  928.     (while map
  929.       (let ((newscore (strokes-rate-stroke stroke (caar map))))
  930.         (if (or (and newscore score (< newscore score))
  931.             (and newscore (null score)))
  932.         (setq score newscore
  933.               command (cdar map)))
  934.         (setq map (cdr map))))
  935.     (if score
  936.         (cons command score)
  937.       nil))
  938.     nil))
  939.  
  940. ;;;###autoload
  941. (defun strokes-read-stroke (&optional prompt event)
  942.   "Read a simple stroke (interactively) and return the stroke.
  943. Optional PROMPT in minibuffer displays before and during stroke reading.
  944. This function will display the stroke interactively as it is being
  945. entered in the strokes buffer if the variable
  946. `strokes-use-strokes-buffer' is non-nil.
  947. Optional EVENT is acceptable as the starting event of the stroke"
  948.   (save-excursion
  949.     (let ((pix-locs nil)
  950.       (grid-locs nil)
  951.       (safe-to-draw-p nil))
  952.       (strokes-while-inhibiting-garbage-collector
  953.        (if strokes-use-strokes-buffer
  954.        ;; switch to the strokes buffer and
  955.        ;; display the stroke as it's being read
  956.        (save-window-excursion
  957.          (set-window-configuration strokes-window-configuration)
  958.          (when prompt
  959.            (setq event (next-command-event event prompt))
  960.            (or (button-press-event-p event)
  961.            (error "You must draw with the mouse")))
  962.          (or event (setq event (next-event nil prompt)
  963.                  safe-to-draw-p t))
  964.          (unwind-protect
  965.          (progn
  966.            (while (not (button-release-event-p event))
  967.              (if (mouse-event-p event)
  968.              (let ((point (event-closest-point event)))
  969.                (if (and point safe-to-draw-p)
  970.                    ;; we can draw that point
  971.                    (progn
  972.                  (goto-char point)
  973.                  (subst-char-in-region point (1+ point) ?\  strokes-character))
  974.                  ;; otherwise, we can start drawing the next time...
  975.                  (setq safe-to-draw-p t))
  976.                (push (cons (event-x-pixel event)
  977.                        (event-y-pixel event))
  978.                  pix-locs))
  979.                ;; otherwise, if it's not a mouse-event...
  980.                (dispatch-event event))
  981.              (setq event (next-event event))))
  982.            ;; protected
  983.            ;; clean up strokes buffer and then bury it.
  984.            (when (equal (buffer-name) strokes-buffer-name)
  985.          (subst-char-in-region (point-min) (point-max) strokes-character ?\ )
  986.          (goto-char (point-min))
  987.          (bury-buffer))))
  988.      ;; Otherwise, don't use strokes buffer and read stroke silently
  989.      (when prompt
  990.        (setq event (next-command-event event prompt))
  991.        (or (button-press-event-p event)
  992.            (error "You must draw with the mouse")))
  993.      (or event (setq event (next-event nil prompt)))
  994.      (while (not (button-release-event-p event))
  995.        (if (mouse-event-p event)
  996.            (push (cons (event-x-pixel event)
  997.                (event-y-pixel event))
  998.              pix-locs)
  999.          (dispatch-event event))
  1000.        (setq event (next-event event)))))
  1001.       (setq grid-locs (strokes-renormalize-to-grid (nreverse pix-locs)))
  1002.       (strokes-fill-stroke (strokes-eliminate-consecutive-redundancies grid-locs)))))
  1003.  
  1004. (defun strokes-read-complex-stroke (&optional prompt event)
  1005.   "Read a complex stroke (interactively) and return the stroke.
  1006. Optional PROMPT in minibuffer displays before and during stroke reading.
  1007. Note that a complex stroke allows the user to pen-up and pen-down.  This
  1008. is implemented by allowing the user to paint with button1 or button2 and
  1009. then complete the stroke with button3.
  1010. Optional EVENT is acceptable as the starting event of the stroke"
  1011.   (save-excursion
  1012.     (save-window-excursion
  1013.       (strokes-while-inhibiting-garbage-collector
  1014.        (set-window-configuration strokes-window-configuration)
  1015.        (let ((pix-locs nil)
  1016.          (grid-locs nil)
  1017.          (safe-to-draw-p nil))
  1018.      (when prompt
  1019.        (setq event (next-command-event event prompt))
  1020.        (or (button-press-event-p event)
  1021.            (error "You must draw with the mouse")))
  1022.      (or event (setq event (next-event nil prompt)
  1023.              safe-to-draw-p t))
  1024.      (unwind-protect
  1025.          (progn
  1026.            (while (not (and (button-press-event-p event)
  1027.                 (eq (event-button event) 3)))
  1028.          (while (not (button-release-event-p event))
  1029.            (if (mouse-event-p event)
  1030.                (let ((point (event-closest-point event)))
  1031.              (if (and point safe-to-draw-p)
  1032.                  ;; we can draw that point
  1033.                  (progn
  1034.                    (goto-char point)
  1035.                    (subst-char-in-region point (1+ point) ?\  strokes-character))
  1036.                ;; otherwise, we can start drawing the next time...
  1037.                (setq safe-to-draw-p t))
  1038.              (push (cons (event-x-pixel event)
  1039.                      (event-y-pixel event))
  1040.                    pix-locs))
  1041.              (dispatch-event event))
  1042.            (setq event (next-event event prompt)))
  1043.          (push strokes-lift pix-locs)
  1044.          (while (not (button-press-event-p event))
  1045.            (dispatch-event event)
  1046.            (setq event (next-event event prompt))))
  1047.            (setq pix-locs (nreverse (cdr pix-locs)))
  1048.            ;; minor bug fix here for when user enters ` *strokes*'
  1049.            ;; buffer with a click instead of a drag...
  1050.            (when (strokes-lift-p (car pix-locs)) 
  1051.          (setq pix-locs (cdr pix-locs)))
  1052.            (setq grid-locs (strokes-renormalize-to-grid pix-locs))
  1053.            (strokes-fill-stroke
  1054.         (strokes-eliminate-consecutive-redundancies grid-locs)))
  1055.        ;; protected
  1056.        (when (equal (buffer-name) strokes-buffer-name)
  1057.          (subst-char-in-region (point-min) (point-max) strokes-character ?\ )
  1058.          (goto-char (point-min))
  1059.          (bury-buffer))))))))
  1060.  
  1061. (defun strokes-execute-stroke (stroke)
  1062.   "Given STROKE, execute the command which corresponds to it.
  1063. The command will be executed provided one exists for that stroke,
  1064. based on the variable `strokes-minimum-match-score'.
  1065. If no stroke matches, nothing is done and return value is nil."
  1066.   (let* ((match (strokes-match-stroke stroke strokes-global-map))
  1067.      (command (car match))
  1068.      (score (cdr match)))
  1069.     (cond ((strokes-click-p stroke)
  1070.        ;; This is the case of a `click' type event.
  1071.        ;; The `sit-for' is a minor frob that has to do with timing
  1072.        ;; problems.  Without the `sit-for', mouse-yank will not
  1073.        ;; yank at the proper location if the user opted for
  1074.        ;; mouse-yank-at-point to be nil (i.e. mouse-yank takes
  1075.        ;; place at pointer position).  The sit-for tells redisplay
  1076.        ;; to be sure to wait for the `*strokes*' buffer to vanish
  1077.        ;; from consideration when deciding on a point to be used
  1078.        ;; for mouse-yank.
  1079.        (sit-for 0)
  1080.        (command-execute strokes-click-command))
  1081.       ((and match (<= score strokes-minimum-match-score))
  1082.        (message "%s" command)
  1083.        (command-execute command))
  1084.       ((null strokes-global-map)
  1085.        (if (file-exists-p strokes-file)
  1086.            (and (y-or-n-p-maybe-dialog-box
  1087.              (format "No strokes loaded.  Load `%s'? "
  1088.                  strokes-file))
  1089.             (strokes-load-user-strokes))
  1090.          (error "No strokes defined; use `global-set-stroke'")))
  1091.       (t
  1092.        (error
  1093.         "No stroke matches; see variable `strokes-minimum-match-score'")
  1094.        nil))))
  1095.  
  1096. ;;;###autoload
  1097. (defun strokes-do-stroke (event)
  1098.   "Read a simple stroke from the user and then exectute its command.
  1099. This must be bound to a mouse event."
  1100.   (interactive "e")
  1101.   (or strokes-mode (strokes-mode t))
  1102.   (strokes-execute-stroke (strokes-read-stroke nil event)))
  1103.  
  1104. ;;;###autoload
  1105. (defun strokes-do-complex-stroke (event)
  1106.   "Read a complex stroke from the user and then exectute its command.
  1107. This must be bound to a mouse event."
  1108.   (interactive "e")
  1109.   (or strokes-mode (strokes-mode t))
  1110.   (strokes-execute-stroke (strokes-read-complex-stroke nil event)))
  1111.  
  1112. ;;;###autoload
  1113. (defun strokes-describe-stroke (stroke)
  1114.   "Displays the command which STROKE maps to, reading STROKE interactively."
  1115.   (interactive
  1116.    (list
  1117.     (strokes-read-complex-stroke
  1118.      "Enter stroke to describe; end with button3...")))
  1119.   (let* ((match (strokes-match-stroke stroke strokes-global-map))
  1120.      (command (or (and (strokes-click-p stroke)
  1121.                strokes-click-command)
  1122.               (car match)))
  1123.      (score (cdr match)))
  1124.     (if (or (and match
  1125.          (<= score strokes-minimum-match-score))
  1126.         (and (strokes-click-p stroke)
  1127.          strokes-click-command))
  1128.     (message "That stroke maps to `%s'" command)
  1129.       (message "That stroke is undefined"))
  1130.     (sleep-for 1)))            ; helpful for recursive edits
  1131.  
  1132. ;;;###autoload
  1133. (defalias 'describe-stroke 'strokes-describe-stroke)
  1134.  
  1135. ;;;###autoload
  1136. (defun strokes-help ()
  1137.   "Get instructional help on using the the `strokes' package."
  1138.   (interactive)
  1139.   (with-displaying-help-buffer
  1140.    (function
  1141.     (lambda ()
  1142.       (save-excursion
  1143.     (let ((helpdoc
  1144.            "This is help for the strokes package.
  1145.  
  1146. If you find something wrong with strokes, or feel that it can be
  1147. improved in some way, then please feel free to email me:
  1148.  
  1149. David Bakhash <cadet@mit.edu>
  1150.  
  1151. or just do
  1152.  
  1153. M-x strokes-report-bug
  1154.  
  1155. ------------------------------------------------------------
  1156.  
  1157. ** Strokes...
  1158.  
  1159. The strokes package allows you to define strokes (that you make with
  1160. the mouse or other pointer device) that XEmacs can interpret as
  1161. corresponding to commands, and then executes the commands.  It does
  1162. character recognition, so you don't have to worry about getting it
  1163. right every time.
  1164.  
  1165. Strokes also allows you to compose documents graphically.  You can
  1166. fully edit documents in Chinese, Japanese, etc. based on XEmacs
  1167. strokes.  Once you've done so, you can ascii compress-and-encode them
  1168. and then safely save them for later use, send letters to friends
  1169. (using XEmacs, of course).  Strokes will later decode these documents,
  1170. extracting the strokes for editing use once again, so the editing
  1171. cycle can continue.
  1172.  
  1173. Strokes are easy to program and fun to use.  To start strokes going,
  1174. you'll want to put the following line in your .emacs file:
  1175.  
  1176. (if window-system
  1177.     (require 'strokes))
  1178.  
  1179. This will load strokes when and only when you start XEmacs on a window
  1180. system (i.e. that has a pointer (mouse) device, etc.).
  1181.  
  1182. To toggle strokes-mode, you just do
  1183.  
  1184. > M-x strokes-mode
  1185.  
  1186. ** Strokes for controlling the behavior of XEmacs...
  1187.  
  1188. When you're ready to start defining strokes, just use the command
  1189.  
  1190. > M-x global-set-stroke
  1191.  
  1192. You will see a ` *strokes*' buffer which is waiting for you to enter in
  1193. your stroke.  When you enter in the stroke, you draw with button1 or
  1194. button2, and then end with button3.  Next, you enter in the command
  1195. which will be executed when that stroke is invoked.  Simple as that.
  1196. For now, try to define a stroke to copy a region.  This is a popular
  1197. edit command, so type
  1198.  
  1199. > M-x global-set-stroke
  1200.  
  1201. Then, in the ` *strokes*' buffer, draw the letter `C' (for `copy')
  1202. and then, when it asks you to enter the command to map that to, type
  1203.  
  1204. > copy-region-as-kill
  1205.  
  1206. That's about as hard as it gets.
  1207. Remember: paint with button1 or button2 and then end with button3.
  1208.  
  1209. If ever you want to know what a certain strokes maps to, then do
  1210.  
  1211. > M-x describe-stroke
  1212.  
  1213. and you can enter in any arbitrary stroke.  Remember: The strokes
  1214. package lets you program in simple and complex (multi-lift) strokes.
  1215. The only difference is how you *invoke* the two.  You will most likely
  1216. use simple strokes, as complex strokes were developed for
  1217. Chinese/Japanese/Korean.  So the middle mouse button (button2) will
  1218. invoke the command `strokes-do-stroke' in buffers where button2 doesn't
  1219. already have a meaning other than its original, which is `mouse-yank'.
  1220. But don't worry: `mouse-yank' will still work with strokes (see the
  1221. variable `strokes-click-command').
  1222.  
  1223. If ever you define a stroke which you don't like, then you can unset
  1224. it with the command
  1225.  
  1226. > M-x strokes-unset-last-stroke
  1227.  
  1228. You can always get an idea of what your current strokes look like with
  1229. the command
  1230.  
  1231. > M-x list-strokes
  1232.  
  1233. Your strokes will be displayed in alphabetical order (based on command
  1234. names) and the beginning of each simple stroke will be marked by a
  1235. color dot.  Since you may have several simple strokes in a complex
  1236. stroke, the dot colors are arranged in the rainbow color sequence,
  1237. `ROYGBIV'.  If you want a listing of your strokes from most recent
  1238. down, then use a prefix argument:
  1239.  
  1240. > C-u M-x list-strokes
  1241.  
  1242. Your strokes are stored as you enter them.  They get saved in a file
  1243. called ~/.strokes, along with other strokes configuration variables.
  1244. You can change this location by setting the variable `strokes-file'.
  1245. You will be prompted to save them when you exit XEmacs, or you can save
  1246. them with
  1247.  
  1248. > M-x save-strokes
  1249.  
  1250. Your strokes get loaded automatically when you enable `strokes-mode'.
  1251. You can also load in your user-defined strokes with
  1252.  
  1253. > M-x load-user-strokes
  1254.  
  1255. ** Strokes for pictographic editing...
  1256.  
  1257. If you'd like to create graphical files with strokes, you'll have to
  1258. be running XEmacs on a window system, with XPM support.  You use the
  1259. binding C-button2 to start drawing your strokes.  These are just
  1260. complex strokes, and thus you continue drawing with buttons 1 or 2 and
  1261. end with button-3.  Then the stroke glyph gets inserted into the
  1262. buffer.  You treat it like any other character, which you can copy,
  1263. paste, delete, move, etc.  The command which is bound to C-button2 is
  1264. called `strokes-compose-complex-stroke'.  When all is done, you may
  1265. want to send the file, or save it.  This is done with
  1266.  
  1267. > M-x strokes-encode-buffer
  1268.  
  1269. Likewise, to decode the strokes from a strokes-encoded buffer you do
  1270.  
  1271. > M-x strokes-decode-buffer
  1272.  
  1273. ** A few more important things...
  1274.  
  1275. o The command `strokes-do-complex-stroke' is invoked with M-button2, so that you
  1276.   can execute complex strokes (i.e. with more than one lift) if preferred.
  1277.  
  1278. o Strokes are a bit computer-dependent in that they depend somewhat on
  1279.   the speed of the computer you're working on.  This means that you
  1280.   may have to tweak some variables.  You can read about them in the
  1281.   commentary of `strokes.el'.  Better to just use apropos and read their
  1282.   docstrings.  All variables/functions start with `strokes'.  The one
  1283.   variable which many people wanted to see was
  1284.   `strokes-use-strokes-buffer' which allows the user to use strokes
  1285.   silently--without displaying the strokes.  All variables can be set
  1286.   by customizing the group named `strokes' via the customization package:
  1287.  
  1288.   > M-x customize"))
  1289.      (princ helpdoc standard-output)))))))
  1290.  
  1291. (defun strokes-report-bug ()
  1292.   "Submit a bug report for strokes."
  1293.   (interactive)
  1294.   (let ((reporter-prompt-for-summary-p t))
  1295.     (or (boundp 'reporter-version)
  1296.     (setq reporter-version
  1297.           "Your version of reporter is obsolete.  Please upgrade."))
  1298.     (reporter-submit-bug-report
  1299.      strokes-bug-address "Strokes"
  1300.      (cons
  1301.       'strokes-version
  1302.       (nconc
  1303.        (mapcar
  1304.     'intern
  1305.     (sort
  1306.      (let (completion-ignore-case)
  1307.        (all-completions "strokes-" obarray 'user-variable-p))
  1308.      'string-lessp))
  1309.        (list 'reporter-version)))
  1310.      (function
  1311.       (lambda ()
  1312.     (save-excursion
  1313.       (mail-position-on-field "subject")
  1314.       (beginning-of-line)
  1315.       (skip-chars-forward "^:\n")
  1316.       (if (looking-at ": Strokes;")
  1317.           (progn
  1318.         (goto-char (match-end 0))
  1319.         (delete-char -1)
  1320.         (insert " " strokes-version " bug:")))))))))
  1321.  
  1322. (defsubst strokes-fill-current-buffer-with-whitespace ()
  1323.   "Erase the contents of the current buffer and fill it with whitespace."
  1324.   (erase-buffer)
  1325.   (loop repeat (frame-height) do
  1326.     (insert-char ?\  (1- (frame-width)))
  1327.     (newline))
  1328.   (goto-char (point-min)))
  1329.  
  1330. (defun strokes-window-configuration-changed-p ()
  1331.   "Non-nil if the `strokes-window-configuration' frame properties changed.
  1332. This is based on the last time the `strokes-window-configuration was updated."
  1333.   (not (and (eq (selected-frame)
  1334.         (plist-get strokes-window-configuration-plist
  1335.                'frame))
  1336.         (eq (frame-height)
  1337.         (plist-get strokes-window-configuration-plist
  1338.                'frame-height))
  1339.         (eq (frame-width)
  1340.         (plist-get strokes-window-configuration-plist
  1341.                'frame-width)))))
  1342.  
  1343. (defun strokes-update-window-configuration-plist ()
  1344.   "Update the `strokes-window-configuration-plist' based on the current state."
  1345.   (plist-put strokes-window-configuration-plist
  1346.          'frame
  1347.          (selected-frame))
  1348.   (plist-put strokes-window-configuration-plist
  1349.          'frame-height
  1350.          (frame-height))
  1351.   (plist-put strokes-window-configuration-plist
  1352.          'frame-width
  1353.          (frame-width)))
  1354.  
  1355. (defun strokes-update-window-configuration ()
  1356.   "Update the `strokes-window-configuration'."
  1357.   (interactive)
  1358.   (let ((current-window (selected-window)))
  1359.     (cond ((or (window-minibuffer-p current-window)
  1360.            (window-dedicated-p current-window))
  1361.        ;; don't try to update strokes window configuration
  1362.        ;; if window is dedicated or a minibuffer
  1363.        nil)
  1364.       ((or (interactive-p)
  1365.            (not (buffer-live-p (get-buffer strokes-buffer-name)))
  1366.            (null strokes-window-configuration))
  1367.        ;; create `strokes-window-configuration' from scratch...
  1368.        (save-excursion
  1369.          (save-window-excursion
  1370.            (get-buffer-create strokes-buffer-name)
  1371.            (set-window-buffer current-window strokes-buffer-name)
  1372.            (delete-other-windows)
  1373.            (fundamental-mode)
  1374.            (auto-save-mode 0)
  1375.            (if (featurep 'font-lock)
  1376.            (font-lock-mode 0))
  1377.            (abbrev-mode 0)
  1378.            (buffer-disable-undo (current-buffer))
  1379.            (setq truncate-lines nil)
  1380.            (strokes-fill-current-buffer-with-whitespace)
  1381.            (setq strokes-window-configuration (current-window-configuration))
  1382.            (strokes-update-window-configuration-plist)
  1383.            (bury-buffer))))
  1384.       ((strokes-window-configuration-changed-p) ; simple update
  1385.        ;; update the strokes-window-configuration for this
  1386.        ;; specific frame...
  1387.        (save-excursion
  1388.          (save-window-excursion
  1389.            (set-window-buffer current-window strokes-buffer-name)
  1390.            (delete-other-windows)
  1391.            (strokes-fill-current-buffer-with-whitespace)
  1392.            (setq strokes-window-configuration (current-window-configuration))
  1393.            (strokes-update-window-configuration-plist)
  1394.            (bury-buffer)))))))
  1395.  
  1396. ;;;###autoload
  1397. (defun strokes-load-user-strokes ()
  1398.   "Load user-defined strokes from file named by `strokes-file'."
  1399.   (interactive)
  1400.   (cond ((and (file-exists-p strokes-file)
  1401.           (file-readable-p strokes-file))
  1402.      (load-file strokes-file))
  1403.     ((interactive-p)
  1404.      (error "Trouble loading user-defined strokes; nothing done"))
  1405.     (t
  1406.      (message "No user-defined strokes, sorry"))))
  1407.  
  1408. ;;;###autoload
  1409. (defalias 'load-user-strokes 'strokes-load-user-strokes)
  1410.  
  1411. (defun strokes-prompt-user-save-strokes ()
  1412.   "Save user-defined strokes to file named by `strokes-file'."
  1413.   (interactive)
  1414.   (save-excursion
  1415.     (let ((current strokes-global-map))
  1416.       (unwind-protect
  1417.       (progn
  1418.         (setq strokes-global-map nil)
  1419.         (strokes-load-user-strokes)
  1420.         (if (and (not (equal current strokes-global-map))
  1421.              (or (interactive-p)
  1422.              (yes-or-no-p-maybe-dialog-box "save your strokes? ")))
  1423.         (progn
  1424.           (require 'pp)        ; pretty-print variables
  1425.           (message "Saving strokes in %s..." strokes-file)
  1426.           (get-buffer-create "*saved-strokes*")
  1427.           (set-buffer "*saved-strokes*")
  1428.           (erase-buffer)
  1429.           (emacs-lisp-mode)
  1430.           (goto-char (point-min))
  1431.           (insert-string
  1432.            ";;   -*- Syntax: Emacs-Lisp; Mode: emacs-lisp -*-\n")
  1433.           (insert-string (format ";;; saved strokes for %s, as of %s\n\n"
  1434.                      (user-full-name)
  1435.                      (format-time-string "%B %e, %Y" nil)))
  1436.           (message "Saving strokes in %s..." strokes-file)
  1437.           (insert-string (format "(setq strokes-global-map '%s)"
  1438.                      (pp current)))
  1439.           (message "Saving strokes in %s..." strokes-file)
  1440.           (indent-region (point-min) (point-max) nil)
  1441.           (write-region (point-min)
  1442.                 (point-max)
  1443.                 strokes-file))
  1444.           (message "(no changes need to be saved)")))
  1445.     ;; protected
  1446.     (if (get-buffer "*saved-strokes*")
  1447.         (kill-buffer (get-buffer "*saved-strokes*")))
  1448.     (setq strokes-global-map current)))))
  1449.  
  1450. (defalias 'save-strokes 'strokes-prompt-user-save-strokes)
  1451.  
  1452. (defun strokes-toggle-strokes-buffer (&optional arg)
  1453.   "Toggle the use of the strokes buffer.
  1454. In other words, toggle the variabe `strokes-use-strokes-buffer'.
  1455. With ARG, use strokes buffer if and only if ARG is positive or true.
  1456. Returns value of `strokes-use-strokes-buffer'."
  1457.   (interactive "P")
  1458.   (setq strokes-use-strokes-buffer
  1459.     (if arg (> (prefix-numeric-value arg) 0)
  1460.       (not strokes-use-strokes-buffer))))
  1461.  
  1462. (defun strokes-xpm-for-stroke (&optional stroke bufname b/w-only)
  1463.   "Create an xpm pixmap for the given STROKE in buffer `*strokes-xpm*'.
  1464. If STROKE is not supplied, then `strokes-last-stroke' will be used.
  1465. Optional BUFNAME to name something else.
  1466. The pixmap will contain time information via rainbow dot colors
  1467. where each individual strokes begins.
  1468. Optional B/W-ONLY non-nil will create a mono pixmap, not intended
  1469. for trying to figure out the order of strokes, but rather for reading
  1470. the stroke as a character in some language."
  1471.   (interactive)
  1472.   (save-excursion
  1473.     (let ((buf (get-buffer-create (or bufname "*strokes-xpm*")))
  1474.       (stroke (strokes-eliminate-consecutive-redundancies
  1475.            (strokes-fill-stroke
  1476.             (strokes-renormalize-to-grid (or stroke
  1477.                              strokes-last-stroke)
  1478.                          31))))
  1479.       (lift-flag t)
  1480.       (rainbow-chars (list ?R ?O ?Y ?G ?B ?P))) ; ROYGBIV w/o indigo
  1481.       (set-buffer buf)
  1482.       (erase-buffer)
  1483.       (insert strokes-xpm-header)
  1484.       (loop repeat 33 do
  1485.         (insert-char ?\")
  1486.         (insert-char ?\  33)
  1487.         (insert "\",")
  1488.         (newline)
  1489.         finally
  1490.         (forward-line -1)
  1491.         (end-of-line)
  1492.         (insert "}\n"))
  1493.       (loop for point in stroke
  1494.         for x = (car-safe point)
  1495.         for y = (cdr-safe point) do
  1496.         (cond ((consp point)
  1497.            ;; draw a point, and possibly a starting-point
  1498.            (if (and lift-flag (not b/w-only))
  1499.                ;; mark starting point with the appropriate color
  1500.                (let ((char (or (car rainbow-chars) ?\.)))
  1501.              (loop for i from 0 to 2 do
  1502.                    (loop for j from 0 to 2 do
  1503.                      (goto-line (+ 16 i y))    
  1504.                      (forward-char (+ 1 j x))
  1505.                      (delete-char 1)
  1506.                      (insert-char char)))
  1507.              (setq rainbow-chars (cdr rainbow-chars)
  1508.                    lift-flag nil))
  1509.              ;; Otherwise, just plot the point...
  1510.              (goto-line (+ 17 y))    
  1511.              (forward-char (+ 2 x))    
  1512.              (subst-char-in-region (point) (1+ (point)) ?\  ?\*)))
  1513.           ((strokes-lift-p point)
  1514.            ;; a lift--tell the loop to X out the next point...
  1515.            (setq lift-flag t))))
  1516.       (when (interactive-p)
  1517.     (require 'xpm-mode)
  1518.     (pop-to-buffer "*strokes-xpm*")
  1519.     ;;    (xpm-mode 1)
  1520.     (xpm-show-image)
  1521.     (goto-char (point-min))))))
  1522.  
  1523. ;;; Strokes Edit stuff... ### NOT IMLEMENTED YET ###
  1524.  
  1525. ;;(defun strokes-edit-quit ()
  1526. ;;  (interactive)
  1527. ;;  (or (one-window-p t 0)
  1528. ;;      (delete-window))
  1529. ;;  (kill-buffer "*Strokes List*"))
  1530.  
  1531. ;;(define-derived-mode edit-strokes-mode list-mode
  1532. ;;  "Edit-Strokes"
  1533. ;;  "Major mode for `edit-strokes' and `list-strokes' buffers.
  1534.  
  1535. ;;Editing commands:
  1536.  
  1537. ;;\\{edit-strokes-mode-map}"
  1538. ;;  (setq truncate-lines nil
  1539. ;;    auto-show-mode nil        ; don't want problems here either
  1540. ;;    mode-popup-menu edit-strokes-menu) ; what about extent-specific stuff?
  1541. ;;  (and (featurep 'menubar)
  1542. ;;       current-menubar
  1543. ;;       (set (make-local-variable 'current-menubar)
  1544. ;;        (copy-sequence current-menubar))
  1545. ;;       (add-submenu nil edit-strokes-menu)))
  1546.  
  1547. ;;(let ((map edit-strokes-mode-map))
  1548. ;;  (define-key map "<" 'beginning-of-buffer)
  1549. ;;  (define-key map ">" 'end-of-buffer)
  1550. ;;  ;;  (define-key map "c" 'strokes-copy-other-face)
  1551. ;;  ;;  (define-key map "C" 'strokes-copy-this-face)
  1552. ;;  ;;  (define-key map "s" 'strokes-smaller)
  1553. ;;  ;;  (define-key map "l" 'strokes-larger)
  1554. ;;  ;;  (define-key map "b" 'strokes-bold)
  1555. ;;  ;;  (define-key map "i" 'strokes-italic)
  1556. ;;  (define-key map "e" 'strokes-list-edit)
  1557. ;;  ;;  (define-key map "f" 'strokes-font)
  1558. ;;  ;;  (define-key map "u" 'strokes-underline)
  1559. ;;  ;;  (define-key map "t" 'strokes-truefont)
  1560. ;;  ;;  (define-key map "F" 'strokes-foreground)
  1561. ;;  ;;  (define-key map "B" 'strokes-background)
  1562. ;;  ;;  (define-key map "D" 'strokes-doc-string)
  1563. ;;  (define-key map "a" 'strokes-global-set-stroke)
  1564. ;;  (define-key map "d" 'strokes-list-delete-stroke)
  1565. ;;  ;;  (define-key map "n" 'strokes-list-next)
  1566. ;;  ;;  (define-key map "p" 'strokes-list-prev)
  1567. ;;  ;;  (define-key map " " 'strokes-list-next)
  1568. ;;  ;;  (define-key map "\C-?" 'strokes-list-prev)
  1569. ;;  (define-key map "g" 'strokes-list-strokes) ; refresh display
  1570. ;;  (define-key map "q" 'strokes-edit-quit)
  1571. ;;  (define-key map [(control c) (control c)] 'bury-buffer))
  1572.  
  1573. ;;;;;###autoload
  1574. ;;(defun strokes-edit-strokes (&optional chronological strokes-map)
  1575. ;;  ;; ### DEAL WITH THE 2nd ARGUMENT ISSUE! ###
  1576. ;;  "Edit strokes in a pop-up buffer containing strokes and their definitions.
  1577. ;;If STROKES-MAP is not given, `strokes-global-map' will be used instead.
  1578.  
  1579. ;;Editing commands:
  1580.  
  1581. ;;\\{edit-faces-mode-map}"
  1582. ;;  (interactive "P")
  1583. ;;  (pop-to-buffer (get-buffer-create "*Strokes List*"))
  1584. ;;  (reset-buffer (current-buffer))    ; handy function from minibuf.el
  1585. ;;  (setq strokes-map (or strokes-map
  1586. ;;            strokes-global-map
  1587. ;;            (progn
  1588. ;;              (strokes-load-user-strokes)
  1589. ;;              strokes-global-map)))
  1590. ;;  (or chronological
  1591. ;;      (setq strokes-map (sort (copy-sequence strokes-map)
  1592. ;;                  'strokes-alphabetic-lessp)))
  1593. ;;  ;;  (push-window-configuration)
  1594. ;;  (insert
  1595. ;;   "Command                                     Stroke\n"
  1596. ;;   "-------                                     ------")
  1597. ;;  (loop for def in strokes-map
  1598. ;;    for i from 0 to (1- (length strokes-map)) do
  1599. ;;    (let ((stroke (car def))
  1600. ;;          (command-name (symbol-name (cdr def))))
  1601. ;;      (strokes-xpm-for-stroke stroke " *strokes-xpm*")
  1602. ;;      (newline 2)
  1603. ;;      (insert-char ?\  45)
  1604. ;;      (beginning-of-line)
  1605. ;;      (insert command-name)
  1606. ;;      (beginning-of-line)
  1607. ;;      (forward-char 45)
  1608. ;;      (set (intern (format "strokes-list-annotation-%d" i))
  1609. ;;           (make-annotation (make-glyph
  1610. ;;                 (list
  1611. ;;                  (vector 'xpm
  1612. ;;                      :data (buffer-substring
  1613. ;;                         (point-min " *strokes-xpm*")
  1614. ;;                         (point-max " *strokes-xpm*")
  1615. ;;                         " *strokes-xpm*"))
  1616. ;;                  [string :data "[Stroke]"]))
  1617. ;;                (point) 'text))
  1618. ;;      (set-annotation-data (symbol-value (intern (format "strokes-list-annotation-%d" i)))
  1619. ;;                   def))
  1620. ;;    finally do (kill-region (1+ (point)) (point-max)))
  1621. ;;  (edit-strokes-mode)
  1622. ;;  (goto-char (point-min)))
  1623.  
  1624. ;;;;;###autoload
  1625. ;;(defalias 'edit-strokes 'strokes-edit-strokes)
  1626.  
  1627. ;;;###autoload
  1628. (defun strokes-list-strokes (&optional chronological strokes-map)
  1629.   "Pop up a buffer containing an alphabetical listing of strokes in STROKES-MAP.
  1630. With CHRONOLOGICAL prefix arg \(\\[universal-argument]\) list strokes
  1631. chronologically by command name.
  1632. If STROKES-MAP is not given, `strokes-global-map' will be used instead."
  1633.   (interactive "P")
  1634.   (setq strokes-map (or strokes-map
  1635.             strokes-global-map
  1636.             (progn
  1637.               (strokes-load-user-strokes)
  1638.               strokes-global-map)))
  1639.   (if (not chronological)
  1640.       ;; then alphabetize the strokes based on command names...
  1641.       (setq strokes-map (sort (copy-sequence strokes-map)
  1642.                   'strokes-alphabetic-lessp)))
  1643.   (push-window-configuration)
  1644.   (set-buffer (get-buffer-create "*Strokes List*"))
  1645.   (setq buffer-read-only nil)
  1646.   (erase-buffer)
  1647.   (insert
  1648.    "Command                                     Stroke\n"
  1649.    "-------                                     ------")
  1650.   (loop for def in strokes-map do
  1651.     (let ((stroke (car def))
  1652.           (command-name (symbol-name (cdr def))))
  1653.       (strokes-xpm-for-stroke stroke " *strokes-xpm*")
  1654.       (newline 2)
  1655.       (insert-char ?\  45)
  1656.       (beginning-of-line)
  1657.       (insert command-name)
  1658.       (beginning-of-line)
  1659.       (forward-char 45)
  1660.       (make-annotation (make-glyph
  1661.                 (list
  1662.                  (vector 'xpm
  1663.                      :data (buffer-substring
  1664.                         (point-min " *strokes-xpm*")
  1665.                         (point-max " *strokes-xpm*")
  1666.                         " *strokes-xpm*"))
  1667.                  [string :data "[Image]"]))
  1668.                (point) 'text))
  1669.     finally do (kill-region (1+ (point)) (point-max)))
  1670.   (view-buffer "*Strokes List*" t)
  1671.   (goto-char (point-min))
  1672.   (define-key view-minor-mode-map [(q)] (lambda ()
  1673.                       (interactive)
  1674.                       (view-quit)
  1675.                       (pop-window-configuration)
  1676.                       ;; (bury-buffer "*Strokes List*")
  1677.                       (define-key view-minor-mode-map [(q)] 'view-quit))))
  1678.  
  1679. (defun strokes-alphabetic-lessp (stroke1 stroke2)
  1680.   "T iff command name for STROKE1 is less than STROKE2's in lexicographic order."
  1681.   (let ((command-name-1 (symbol-name (cdr stroke1)))
  1682.     (command-name-2 (symbol-name (cdr stroke2))))
  1683.     (string-lessp command-name-1 command-name-2)))
  1684.  
  1685. ;;;###autoload
  1686. (defalias 'list-strokes 'strokes-list-strokes)
  1687.  
  1688. ;;;###autoload
  1689. (defun strokes-mode (&optional arg)
  1690.   "Toggle strokes being enabled.
  1691. With ARG, turn strokes on if and only if ARG is positive or true.
  1692. Note that `strokes-mode' is a global mode.  Think of it as a minor
  1693. mode in all buffers when activated.
  1694. By default, strokes are invoked with mouse button-2.  You can define
  1695. new strokes with
  1696.  
  1697. > M-x global-set-stroke
  1698.  
  1699. To use strokes for pictographic editing, such as Chinese/Japanese, use
  1700. Sh-button-2, which draws strokes and inserts them.  Encode/decode your
  1701. strokes with
  1702.  
  1703. > M-x strokes-encode-buffer
  1704. > M-x strokes-decode-buffer"
  1705.   (interactive "P")
  1706.   (let ((on-p (if arg
  1707.           (> (prefix-numeric-value arg) 0)
  1708.         (not strokes-mode))))
  1709.     (cond ((not (device-on-window-system-p))
  1710.        (warn "Can't use strokes without windows"))
  1711.       (on-p                ; turn on strokes
  1712.        (strokes-insinuate)
  1713.        (and (file-exists-p strokes-file)
  1714.         (null strokes-global-map)
  1715.         (strokes-load-user-strokes))
  1716.        (add-hook 'kill-emacs-query-functions
  1717.              'strokes-prompt-user-save-strokes)
  1718.        (add-hook 'select-frame-hook
  1719.              'strokes-update-window-configuration)
  1720.        (strokes-update-window-configuration)
  1721.        (define-key global-map [(button2)] 'strokes-do-stroke)
  1722.        (define-key global-map [(meta button2)] 'strokes-do-complex-stroke)
  1723.        ;;       (define-key global-map [(control button2)] 'strokes-do-complex-stroke)
  1724.        (define-key global-map [(control button2)]
  1725.          'strokes-compose-complex-stroke)
  1726.        (ad-activate-regexp "^strokes-") ; advise button2 commands
  1727.        (setq strokes-mode t))
  1728.       (t                ; turn off strokes
  1729.        (if (get-buffer strokes-buffer-name)
  1730.            (kill-buffer (get-buffer strokes-buffer-name)))
  1731.        (remove-hook 'select-frame-hook
  1732.             'strokes-update-window-configuration)
  1733.        (if (string-match "^strokes-" (symbol-name (key-binding [(button2)])))
  1734.            (define-key global-map [(button2)] strokes-click-command))
  1735.        (if (string-match "^strokes-" (symbol-name (key-binding [(meta button2)])))
  1736.            (global-unset-key [(meta button2)]))
  1737.        (if (string-match "^strokes-" (symbol-name (key-binding [(control button2)])))
  1738.            (global-unset-key [(control button2)]))
  1739.        ;;       (if (string-match "^strokes-" (symbol-name (key-binding [(shift button2)])))    
  1740.        ;;           (global-unset-key [(shift button2)]))
  1741.        (ad-deactivate-regexp "^strokes-") ; unadvise strokes-button2 commands
  1742.        (setq strokes-mode nil))))
  1743.   (redraw-modeline))
  1744.  
  1745. (add-minor-mode 'strokes-mode strokes-modeline-string nil nil 'strokes-mode)
  1746.  
  1747. ;;;; strokes-xpm stuff (later may be separate)...
  1748.  
  1749. ;; This is the stuff that will eventuall be used for composing letters in
  1750. ;; any language, compression, decompression, graphics, editing, etc.
  1751.  
  1752. (require 'atomic-extents)        ; might as well say
  1753.                     ; (require 'not-so-atomic-extents)
  1754.                     ; but what can you do?
  1755.  
  1756. ;;(unless (find-face 'strokes-char-face)
  1757.  
  1758. (defface strokes-char-face '((t (:background "lightgray")))
  1759.   "Face for strokes characters."
  1760.   :group 'strokes)
  1761.  
  1762. (defconst strokes-char-table (make-char-table 'generic) ;
  1763.   "The table which stores values for the character keys.")
  1764. (put-char-table ?0 0 strokes-char-table)
  1765. (put-char-table ?1 1 strokes-char-table)
  1766. (put-char-table ?2 2 strokes-char-table)
  1767. (put-char-table ?3 3 strokes-char-table)
  1768. (put-char-table ?4 4 strokes-char-table)
  1769. (put-char-table ?5 5 strokes-char-table)
  1770. (put-char-table ?6 6 strokes-char-table)
  1771. (put-char-table ?7 7 strokes-char-table)
  1772. (put-char-table ?8 8 strokes-char-table)
  1773. (put-char-table ?9 9 strokes-char-table)
  1774. (put-char-table ?a 10 strokes-char-table)
  1775. (put-char-table ?b 11 strokes-char-table)
  1776. (put-char-table ?c 12 strokes-char-table)
  1777. (put-char-table ?d 13 strokes-char-table)
  1778. (put-char-table ?e 14 strokes-char-table)
  1779. (put-char-table ?f 15 strokes-char-table)
  1780. (put-char-table ?g 16 strokes-char-table)
  1781. (put-char-table ?h 17 strokes-char-table)
  1782. (put-char-table ?i 18 strokes-char-table)
  1783. (put-char-table ?j 19 strokes-char-table)
  1784. (put-char-table ?k 20 strokes-char-table)
  1785. (put-char-table ?l 21 strokes-char-table)
  1786. (put-char-table ?m 22 strokes-char-table)
  1787. (put-char-table ?n 23 strokes-char-table)
  1788. (put-char-table ?o 24 strokes-char-table)
  1789. (put-char-table ?p 25 strokes-char-table)
  1790. (put-char-table ?q 26 strokes-char-table)
  1791. (put-char-table ?r 27 strokes-char-table)
  1792. (put-char-table ?s 28 strokes-char-table)
  1793. (put-char-table ?t 29 strokes-char-table)
  1794. (put-char-table ?u 30 strokes-char-table)
  1795. (put-char-table ?v 31 strokes-char-table)
  1796. (put-char-table ?w 32 strokes-char-table)
  1797. (put-char-table ?x 33 strokes-char-table)
  1798. (put-char-table ?y 34 strokes-char-table)
  1799. (put-char-table ?z 35 strokes-char-table)
  1800. (put-char-table ?A 36 strokes-char-table)
  1801. (put-char-table ?B 37 strokes-char-table)
  1802. (put-char-table ?C 38 strokes-char-table)
  1803. (put-char-table ?D 39 strokes-char-table)
  1804. (put-char-table ?E 40 strokes-char-table)
  1805. (put-char-table ?F 41 strokes-char-table)
  1806. (put-char-table ?G 42 strokes-char-table)
  1807. (put-char-table ?H 43 strokes-char-table)
  1808. (put-char-table ?I 44 strokes-char-table)
  1809. (put-char-table ?J 45 strokes-char-table)
  1810. (put-char-table ?K 46 strokes-char-table)
  1811. (put-char-table ?L 47 strokes-char-table)
  1812. (put-char-table ?M 48 strokes-char-table)
  1813. (put-char-table ?N 49 strokes-char-table)
  1814. (put-char-table ?O 50 strokes-char-table)
  1815. (put-char-table ?P 51 strokes-char-table)
  1816. (put-char-table ?Q 52 strokes-char-table)
  1817. (put-char-table ?R 53 strokes-char-table)
  1818. (put-char-table ?S 54 strokes-char-table)
  1819. (put-char-table ?T 55 strokes-char-table)
  1820. (put-char-table ?U 56 strokes-char-table)
  1821. (put-char-table ?V 57 strokes-char-table)
  1822. (put-char-table ?W 58 strokes-char-table)
  1823. (put-char-table ?X 59 strokes-char-table)
  1824. (put-char-table ?Y 60 strokes-char-table)
  1825. (put-char-table ?Z 61 strokes-char-table)
  1826.  
  1827. (defconst strokes-base64-chars
  1828.   ;; I can easily have made this a vector of single-character strings,
  1829.   ;; like (vector "0" "1" "2" ...), and then the program would run
  1830.   ;; faster since it wouldn't then have to call `char-to-string' when it
  1831.   ;; did the `concat'.  I left them as chars here because I want
  1832.   ;; *them* to change `concat' so that it accepts chars and deals with
  1833.   ;; them properly. i.e. the form: (concat "abc" ?T "xyz") should
  1834.   ;; return "abcTxyz" NOT "abc84xyz" (XEmacs 19.*) and NOT an error
  1835.   ;; (XEmacs 20.*).
  1836.   ;;  (vector "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"
  1837.   ;;      "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o"
  1838.   ;;      "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" "A" "B" "C" "D"
  1839.   ;;      "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S"
  1840.   ;;      "T" "U" "V" "W" "X" "Y" "Z")
  1841.   (vector ?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9
  1842.       ?a ?b ?c ?d ?e ?f ?g ?h ?i ?j ?k ?l ?m ?n ?o ?p ?q ?r ?s ?t ?u ?v ?w ?x ?y ?z
  1843.       ?A ?B ?C ?D ?E ?F ?G ?H ?I ?J ?K ?L ?M ?N ?O ?P ?Q ?R ?S ?T ?U ?V ?W ?X ?Y ?Z)
  1844.   "Character vector for fast lookup of base-64 encoding of numbers in [0,61].")
  1845.  
  1846. (defsubst strokes-xpm-char-on-p (char)
  1847.   "Non-nil if CHAR represents an `on' bit in the xpm."
  1848.   (char= char ?*))
  1849.  
  1850. (defsubst strokes-xpm-char-bit-p (char)
  1851.   "Non-nil if CHAR represents an `on' or `off' bit in the xpm."
  1852.   (or (char= char ?\ )
  1853.       (char= char ?*)))
  1854.  
  1855. ;;(defsubst strokes-xor (a b)  ### Should I make this an inline function? ###
  1856. ;;  "T iff one and only one of A and B is non-nil; otherwise, returns nil.
  1857. ;;NOTE: Don't use this as a numeric xor since it treats all non-nil
  1858. ;;      values as t including `0' (zero)."
  1859. ;;  (eq (null a) (not (null b))))
  1860.  
  1861. (defsubst strokes-xpm-encode-length-as-string (length)
  1862.   "Given some LENGTH in [0,62) do a fast lookup of it's encoding."
  1863.   (char-to-string (aref strokes-base64-chars length)))
  1864.            
  1865. (defsubst strokes-xpm-decode-char (character)
  1866.   "Given a CHARACTER, do a fast lookup to find its corresponding integer value."
  1867.   (get-char-table character strokes-char-table))
  1868.            
  1869. (defun strokes-xpm-to-compressed-string (&optional xpm-buffer)
  1870.   "Convert the xpm in XPM-BUFFER into a compressed string representing the stroke.
  1871. XPM-BUFFER is an optional argument, and defaults to `*strokes-xpm*'."
  1872.   (save-excursion
  1873.     (set-buffer (setq xpm-buffer (or xpm-buffer "*strokes-xpm*")))
  1874.     (goto-char (point-min))
  1875.     (search-forward "/* pixels */")    ; skip past header junk
  1876.     (forward-char 2)
  1877.     ;; a note for below:
  1878.     ;; the `current-char' is the char being counted -- NOT the char at (point)
  1879.     ;; which happens to be called `char-at-point'
  1880.     (let ((compressed-string "+/")    ; initialize the output
  1881.       (count 0)            ; keep a current count of
  1882.                     ; `current-char'
  1883.       (last-char-was-on-p t)           ; last entered stream
  1884.                     ; represented `on' bits
  1885.       (current-char-is-on-p nil)    ; current stream represents `on' bits
  1886.       (char-at-point (char-after)))    ; read the first char
  1887.       (while (not (char= char-at-point ?})) ; a `}' denotes the
  1888.                     ; end of the pixmap
  1889.     (cond ((zerop count)        ; must restart counting
  1890.            ;; check to see if the `char-at-point' is an actual pixmap bit
  1891.            (when (strokes-xpm-char-bit-p char-at-point)
  1892.          (setq count 1
  1893.                current-char-is-on-p (strokes-xpm-char-on-p char-at-point)))           
  1894.            (forward-char 1))
  1895.           ((= count 61)        ; maximum single char's
  1896.                     ; encoding length
  1897.            (setq compressed-string (concat compressed-string
  1898.                            ;; add a zero-length
  1899.                            ;; encoding when
  1900.                            ;; necessary
  1901.                            (when (eq last-char-was-on-p
  1902.                              current-char-is-on-p)
  1903.                          ;; "0"
  1904.                          (strokes-xpm-encode-length-as-string 0))
  1905.                            (strokes-xpm-encode-length-as-string 61))
  1906.              last-char-was-on-p current-char-is-on-p
  1907.              count 0))        ; note that we just set
  1908.                     ; count=0 and *don't* advance
  1909.                     ; (point)
  1910.           ((strokes-xpm-char-bit-p char-at-point) ; an actual xpm bit
  1911.            (if (eq current-char-is-on-p
  1912.                (strokes-xpm-char-on-p char-at-point))
  1913.            ;; yet another of the same bit-type, so we continue
  1914.            ;; counting...
  1915.            (progn
  1916.              (incf count)
  1917.              (forward-char 1))
  1918.          ;; otherwise, it's the opposite bit-type, so we do a
  1919.          ;; write and then restart count ### NOTE (for myself
  1920.          ;; to be aware of) ### I really should advance
  1921.          ;; (point) in this case instead of letting another
  1922.          ;; iteration go through and letting the case: count=0
  1923.          ;; take care of this stuff for me.  That's why
  1924.          ;; there's no (forward-char 1) below.
  1925.          (setq compressed-string (concat compressed-string
  1926.                          ;; add a zero-length
  1927.                          ;; encoding when
  1928.                          ;; necessary
  1929.                          (when (eq last-char-was-on-p
  1930.                                current-char-is-on-p)
  1931.                            ;; "0"
  1932.                            (strokes-xpm-encode-length-as-string 0))
  1933.                          (strokes-xpm-encode-length-as-string count))
  1934.                count 0
  1935.                last-char-was-on-p current-char-is-on-p)))
  1936.           (t            ; ELSE it's some other useless
  1937.                     ; char, like `"' or `,'
  1938.            (forward-char 1)))
  1939.     (setq char-at-point (char-after)))
  1940.       (concat compressed-string
  1941.           (when (> count 0)
  1942.         (concat (when (eq last-char-was-on-p
  1943.                   current-char-is-on-p)
  1944.               ;; "0"
  1945.               (strokes-xpm-encode-length-as-string 0))
  1946.             (strokes-xpm-encode-length-as-string count)))
  1947.           "/"))))
  1948.  
  1949. ;;;###autoload
  1950. (defun strokes-decode-buffer (&optional buffer force)
  1951.   "Decode stroke strings in BUFFER and display their corresponding glyphs.
  1952. Optional BUFFER defaults to the current buffer.
  1953. Optional FORCE non-nil will ignore the buffer's read-only status."
  1954.   (interactive)
  1955.   ;;  (interactive "*bStrokify buffer: ")
  1956.   (save-excursion
  1957.     (set-buffer (setq buffer (get-buffer (or buffer (current-buffer)))))
  1958.     (when (or (not buffer-read-only)
  1959.           force
  1960.           inhibit-read-only
  1961.           (y-or-n-p-maybe-dialog-box
  1962.            (format "Buffer %s is read-only.  Strokify anyway? " buffer)))
  1963.       (let ((inhibit-read-only t))
  1964.     (message "Strokifying %s..." buffer)
  1965.     (goto-char (point-min))
  1966.     (let (ext string)
  1967.       ;; The comment below is what i'd have to do if I wanted to
  1968.       ;; deal with random newlines in the midst of the compressed
  1969.       ;; strings.  If I do this, I'll also have to change
  1970.       ;; `strokes-xpm-to-compress-string' to deal with the newline,
  1971.       ;; and possibly other whitespace stuff.  YUCK!
  1972.       ;;      (while (re-search-forward "\\+/\\(\\w\\|\\)+/" nil t nil (get-buffer buffer))
  1973.       (while (re-search-forward "\\+/\\w+/" nil t nil buffer)
  1974.         (setq string (buffer-substring (+ 2 (match-beginning 0))
  1975.                        (1- (match-end 0))))
  1976.         (strokes-xpm-for-compressed-string string " *strokes-xpm*")
  1977.         (replace-match " ")
  1978.         (setq ext (make-extent (1- (point)) (point)))
  1979.         (set-extent-property ext 'type 'stroke-glyph)
  1980.         (set-extent-property ext 'start-open t)
  1981.         (set-extent-property ext 'end-open t)
  1982.         (set-extent-property ext 'detachable t)
  1983.         (set-extent-property ext 'duplicable t)
  1984.         (set-extent-property ext 'data string)
  1985.         (set-extent-face ext 'default)
  1986.         (set-extent-end-glyph ext (make-glyph
  1987.                        (list
  1988.                     (vector 'xpm
  1989.                         :data (buffer-substring
  1990.                                (point-min " *strokes-xpm*")
  1991.                                (point-max " *strokes-xpm*")
  1992.                                " *strokes-xpm*"))
  1993.                     [string :data "[Stroke]"])))))
  1994.     (message "Strokifying %s...done" buffer)))))
  1995.  
  1996. (defun strokes-encode-buffer (&optional buffer force)
  1997.   "Convert the glyphs in BUFFER to thier base-64 ASCII representations.
  1998. Optional BUFFER defaults to the current buffer.
  1999. Optional FORCE non-nil will ignore the buffer's read-only status."
  2000.   ;; ### NOTE !!! ### (for me)
  2001.   ;; For later on, you can/should make the inserted strings atomic
  2002.   ;; extents, so that the users have a clue that they shouldn't be
  2003.   ;; editing inside them.  Plus, if you make them extents, you can
  2004.   ;; very easily just hide the glyphs, so if you unstrokify, and the
  2005.   ;; restrokify, then those that already are glyphed don't need to be
  2006.   ;; re-calculated, etc.  It's just nicer that way.  The only things
  2007.   ;; to worry about is cleanup (i.e. do the glyphs get gc'd when the
  2008.   ;; buffer is killed?
  2009.   ;;  (interactive "*bUnstrokify buffer: ")
  2010.   (interactive)
  2011.   (save-excursion
  2012.     (set-buffer (setq buffer (or buffer (current-buffer))))
  2013.     (when (or (not buffer-read-only)
  2014.           force
  2015.           inhibit-read-only
  2016.           (y-or-n-p-maybe-dialog-box
  2017.            (format "Buffer %s is read-only.  Encode anyway? " buffer)))
  2018.       (message "Encoding strokes in %s..." buffer)
  2019.       ;;      (map-extents
  2020.       ;;       (lambda (ext buf)
  2021.       ;;     (when (eq (extent-property ext 'type) 'stroke-glyph)
  2022.       ;;       (goto-char (extent-start-position ext))
  2023.       ;;       (delete-char 1)  ; ### What the hell do I do here? ###
  2024.       ;;       (insert "+/" (extent-property ext 'data) "/")
  2025.       ;;       (delete-extent ext))))))
  2026.       (let ((inhibit-read-only t)
  2027.         (start nil))
  2028.     (loop repeat 2 do        ; ### KLUDGE!!! This it pure crap! ###
  2029.           (map-extents
  2030.            (lambda (ext buf)
  2031.          (when (eq (extent-property ext 'type) 'stroke-glyph)
  2032.            (setq start (goto-char (extent-start-position ext)))
  2033.            ;;       (insert "+/" (extent-property ext 'data) "/")
  2034.            (insert-string "+/")
  2035.            (insert-string (extent-property ext 'data))
  2036.            (insert-string "/")
  2037.            (delete-char 1)
  2038.            (set-extent-endpoints ext start (point))
  2039.            (set-extent-property ext 'type 'stroke-string)
  2040.            (set-extent-property ext 'atomic t)
  2041.            ;;       (set-extent-property ext 'read-only t)
  2042.            (set-extent-face ext 'strokes-char-face)
  2043.            (set-extent-property ext 'stroke-glyph (extent-end-glyph ext))
  2044.            (set-extent-end-glyph ext nil))))))
  2045.       (message "Encoding strokes in %s...done" buffer))))
  2046.  
  2047. (defun strokes-xpm-for-compressed-string (compressed-string &optional bufname)
  2048.   "Convert the stroke represented by COMPRESSED-STRING into an xpm.
  2049. Store xpm in buffer BUFNAME if supplied \(default is `*strokes-xpm*'\)"
  2050.   (save-excursion
  2051.     (or bufname (setq bufname "*strokes-xpm*"))
  2052.     (erase-buffer (set-buffer (get-buffer-create bufname)))
  2053.     (insert compressed-string)
  2054.     (goto-char (point-min))
  2055.     (let ((current-char-is-on-p nil))
  2056.       (while (not (eobp))
  2057.     (insert-char
  2058.      (if current-char-is-on-p
  2059.          ?*
  2060.        ?\ )
  2061.      (strokes-xpm-decode-char (char-after)))
  2062.     (delete-char 1)
  2063.     (setq current-char-is-on-p (not current-char-is-on-p)))
  2064.       (goto-char (point-min))
  2065.       (loop repeat 33 do
  2066.         (insert-char ?\")
  2067.         (forward-char 33)
  2068.         (insert "\",\n"))
  2069.       (goto-char (point-min))
  2070.       (insert strokes-xpm-header))))
  2071.  
  2072. ;;;###autoload
  2073. (defun strokes-compose-complex-stroke ()
  2074.   ;; ### NOTE !!! ###
  2075.   ;; Even though we have lexical scoping, it's somewhat ugly how I
  2076.   ;; pass around variables in the global name space.  I can/should
  2077.   ;; change this.
  2078.   "Read a complex stroke and insert its glyph into the current buffer."
  2079.   (interactive "*")
  2080.   (let ((strokes-grid-resolution 33))
  2081.     (strokes-read-complex-stroke)
  2082.     (strokes-xpm-for-stroke nil " *strokes-xpm*" t)
  2083.     (insert (strokes-xpm-to-compressed-string " *strokes-xpm*"))
  2084.     (strokes-decode-buffer)))
  2085.  
  2086. (provide 'strokes)
  2087. (run-hooks 'strokes-load-hook)
  2088.  
  2089. ;;; strokes.el ends here
  2090.